ARTICLE DETAIL

资讯详情

深耕网站建设、视觉设计与SEO优化的一线实战洞察。

IoT-For-Beginners 农场项目:用 Azure Functions 将土壤湿度继电器控制逻辑迁移到云端的实战指南

IoT-For-Beginners 农场项目:用 Azure Functions 将土壤湿度继电器控制逻辑迁移到云端的实战指南 IoT-For-Beginners 农场项目用 Azure Functions 将土壤湿度继电器控制逻辑迁移到云端的实战指南【免费下载链接】IoT-For-Beginners12 Weeks, 24 Lessons, IoT for All!项目地址: https://gitcode.com/GitHub_Trending/io/IoT-For-Beginners本文基于 IoT-For-Beginners 课程第 2 周“数字化农业2-farm”系列的第 5 课讲解如何将监听土壤湿度遥测并控制继电器开关的服务端逻辑从本地 MQTT 常驻进程迁移为运行在 Azure 的 serverless 函数涵盖 serverless 事件驱动模型、Azure Functions 本地开发环境搭建、IoT Hub 事件触发器Event Hub 兼容端点配置、通过 Registry Manager 向设备发送直接方法Direct Method请求以及最终将函数应用部署到云端的完整命令流程。读完后你将掌握一套可复用的“IoT 遥测进云端、云端下发命令到设备”的 serverless 架构方案。课程背景从常驻 MQTT 服务端到事件驱动云端在上一课2-farm 第 4 课中你已经完成了两件事将土壤湿度监测与继电器relay控制接入了云端 IoT 服务并编写了一个本地 MQTT 服务端程序来监听设备消息、按湿度值开关继电器。本课的目标是把这段“服务端代码”从你自己电脑上常驻运行的脚本改造为云上的 serverless 函数——只在设备消息到达时才执行不再需要一台时刻开着的服务器。本课的内容脉络什么是 serverless事件驱动的计算模型创建 serverless 应用Azure Functions创建 IoT Hub 触发器Event Hub 兼容端点从 serverless 代码发送直接方法请求Direct Method将 serverless 代码部署到云端。什么是 serverlessServerless无服务器计算的核心是编写小的代码块由云端在特定事件发生时才执行。事件到来时你的代码被调用并拿到事件数据事件来源可以是多种多样的——Web 请求、队列消息、数据库变更或者本课的场景IoT 设备发往 IoT 服务的消息。从源码与配置角度看这套模型有几个关键特性按需执行与自动伸缩代码只在事件发生时执行其余时间不占资源。当大量事件同时到达时云厂商可以把同一个函数执行任意多次分布到不同的服务器上。用数据库触发器trigger做类比更容易理解同样是“由事件如插入一行激活的代码”。无本地状态正因为每次执行是独立的如果需要跨事件共享信息必须外置到数据库等持久化存储而不能放在进程内存里。FaaSFunctions as a Service你的代码被写成一个接收事件详情作为参数parameter的函数可以用多种语言编写。名称的由来“serverless”并非没有服务器而是开发者不需要关心服务器——云厂商的 serverless runtime 负责调度服务器、网络、存储、CPU 和内存。计费方式是按代码执行时间和占用内存付费而不是按服务器付费因此对低频 IoT 场景设备几分钟才上报一次遥测成本极低部分云厂商还提供每月百万次级别的免费执行额度。对 IoT 开发者特别契合你可以写一个函数让连接到你 IoT 服务的每一个设备上报消息时都触发它——代码处理所有消息但只在必要时执行。顺带一提serverless 模式还可以扩展到其他云资源例如按查询次数计费的 serverless 数据库费用通常与查询复杂度相关按主键取一行的成本远低于跨表扫描海量行。创建 serverless 应用Azure FunctionsMicrosoft 的 serverless 计算服务名为Azure Functions。它支持 Python、JavaScript、TypeScript、C#、F#、Java、PowerShell 等语言还支持自定义处理器custom handlers——理论上任何能响应 HTTP 请求的语言都可以写函数。本课使用Python。函数应用由一个或多个 **触发器trigger**组成响应事件的函数。一个应用可以有多个触发器它们共享同一份应用级配置——例如在应用配置文件中写入 IoT Hub 连接信息供应用内所有函数使用。任务一安装 Azure Functions 工具链注意截至课程编写时Azure Functions 工具链在 Apple Silicon 的 Mac 上配合 Python 项目尚不能完全工作建议使用 Intel Mac、Windows 或 Linux 环境。本地开发是 Azure Functions 的重要特性云端使用的同一套运行时可以在你的电脑上启动让你编写响应 IoT 消息的代码、本地测试甚至断点调试满意后再部署到云端。工具链包含三部分Azure Functions Core Tools命令行工具func按 Azure 官方“本地运行 Azure Functions”文档安装VS Code 的 Azure Functions 扩展提供创建、调试、部署支持Azurite 存储模拟器函数应用部署到云端后会使用少量云存储来存放文件和日志本地运行时用 Azurite 模拟这部分云存储。安装 Azurite 的步骤# Azurite 是 Node.js 应用先确保已安装 Node.js npm install -g azurite # 创建数据存放目录 mkdir azurite # 启动 Azurite 并指定数据目录 azurite --location azurite启动成功后会看到三个服务分别监听在127.0.0.1的 10000Blob、10001Queue、10002Table端口➜ ~ azurite --location azurite Azurite Blob service is starting at http://127.0.0.1:10000 Azurite Blob service is successfully listening at http://127.0.0.1:10000 Azurite Queue service is starting at http://127.0.0.1:10001 Azurite Queue service is successfully listening at http://127.0.0.1:10001 Azurite Table service is starting at http://127.0.0.1:10002 Azurite Table service is successfully listening at http://127.0.0.1:10002在 Azure 中Azure Functions 使用的存储是一个存储账户Storage Account可以存放文件、表数据或队列并且不同应用之间可以共享同一个账户。任务二创建 Azure Functions 项目创建应用目录并进入命名为soil-moisture-triggermkdir soil-moisture-trigger cd soil-moisture-trigger创建并激活 Python 虚拟环境python3 -m venv .venvWindowsCommand Prompt.venv\Scripts\activate.batWindowsPowerShell.\.venv\Scripts\Activate.ps1macOS / Linuxsource ./.venv/bin/activate初始化函数应用func init --worker-runtime python soil-moisture-trigger这会生成三个文件仓库中的示例项目 soil-moisture-trigger 保留了完整结构可以对照查看host.json— 应用级设置local.settings.json— 本地开发设置例如 IoT Hub 连接串requirements.txt— 依赖的 Pip 包清单。仓库中随课程提供的 host.json 包含两个关键配置logging.applicationInsights的采样设置默认启用、排除Request类型以及extensionBundle指定的扩展包版本范围[2.*, 3.0.0)——后者决定了事件触发器等扩展组件的兼容性。而 requirements.txt 只有两行依赖并特别注明“不要包含azure-functions-worker因为它可能与 Azure Functions 平台冲突”# Do not include azure-functions-worker as it may conflict with the Azure Functions platform azure-functions azure-iot-hub更新local.settings.json让它连接 Azurite 开发存储AzureWebJobsStorage: UseDevelopmentStoragetrue,仓库示例中的完整 local.settings.json 展示了最终形态Values段共 4 个键FUNCTIONS_WORKER_RUNTIME值为python、AzureWebJobsStorage本地开发存储、IOT_HUB_CONNECTION_STRING与REGISTRY_MANAGER_CONNECTION_STRING后两者是本课后续填入的连接串。安装依赖并启动运行时验证pip install -r requirements.txt func start预期输出未创建函数时(.venv) ➜ soil-moisture-trigger func start Found Python version 3.9.1 (python3). Azure Functions Core Tools Core Tools Version: 3.0.3442 Commit hash: 6bfab24b2743f8421475d996402c398d2fe4a9e0 (64-bit) Function Runtime Version: 3.0.15417.0 [2021-05-05T01:24:46.795Z] No job functions found.两个常见提示如果弹出防火墙通知请允许func应用访问网络它需要读写网络macOS 上可能出现Cannot create directory for shared memory usage: /dev/shm/AzureFunctions ... Operation not permitted警告只要 Functions 应用能正常启动并列出函数即可忽略。按ctrlc停止应用然后在 VS Code 中打开当前目录可执行code .。VS Code 识别出这是 Functions 项目后会弹出初始化提示Detected an Azure Functions Project in folder soil-moisture-trigger that may have been created outside of VS Code. Initialize for optimal use with VS Code?选择Yes并确认 VS Code 终端中虚拟环境处于激活状态。创建 IoT Hub 事件触发器Functions 应用是 serverless 代码的“外壳”。要响应 IoT Hub 的事件需要给它加一个 IoT Hub 触发器。这里有一个关键的架构事实读取 IoT Hub 消息的方式与消费 Azure Event Hubs 完全相同——因为 IoT Hub 本身就是构建在 Azure Event Hubs 之上的服务Event Hubs 负责消息的收发IoT Hub 在其上增加了面向 IoT 设备的能力。因此你的触发器实际连接的是 IoT Hub 的event hub 兼容端点event hub compatible endpoint。鉴权方面设备和你的服务端代码一样都需要包含密钥的连接串才能连接。默认拿到的连接串具有iothubowner权限对 IoT Hub 的完全控制权生产实践中应使用满足需求的最小权限——这一点在后续课程中会深入展开。触发器连接成功后函数内的代码会对每个发往 IoT Hub 的消息无论来自哪个设备被调用一次触发器把消息作为参数传入。任务三获取 Event Hub 兼容端点的连接串从 VS Code 终端执行hub_name替换为你在上一课创建的 IoT Hub 名称az iot hub connection-string show --default-eventhub \ --output table \ --hub-name hub_name在local.settings.json的Values段中新增记得给上一行补上逗号保持 JSON 合法IOT_HUB_CONNECTION_STRING: connection string任务四创建事件触发器在soil-moisture-trigger目录下执行func new --name iot-hub-trigger --template Azure Event Hub trigger这会创建iot-hub-trigger目录。由于触发器连接的是 IoT Hub 的 Event Hub 兼容端点所以使用的是Event Hub 触发器模板没有专门的 IoT Hub 触发器。目录内包含两个文件__init__.py— 按 Python 包命名约定将该目录变成模块包含触发器本身。模板生成的初始代码import logging import azure.functions as func def main(event: func.EventHubEvent): logging.info(Python EventHub trigger processed an event: %s, event.get_body().decode(utf-8))触发器的核心是main函数每当有消息发往 IoT Hub该函数就被调用消息作为event参数传入并携带与上一课看到的 MQTT 消息属性相同的标注annotations。初始版本只做一件事——把事件写进日志。function.json— 触发器配置核心是bindings段。binding 是 Azure Functions 与 Azure 其他服务之间连接关系的术语本例是入站inbinding函数从 Event Hub 接收数据也可以配置出站outbinding例如让函数返回值自动写入数据库。仓库中的 function.json 是修正后的最终版本{ scriptFile: __init__.py, bindings: [ { type: eventHubTrigger, name: event, direction: in, eventHubName: , connection: IOT_HUB_CONNECTION_STRING, cardinality: one, consumerGroup: $Default, dataType: binary } ] }各字段含义字段作用type: eventHubTrigger告诉函数监听来自 Event Hub 的事件name: event事件参数的名字必须与main函数的参数名一致direction: in入站 binding数据从 Event Hub 流入函数connection: IOT_HUB_CONNECTION_STRING连接串的设置项名称不是连接串本身本地运行时从local.settings.json读取eventHubName: 必须置空连接串中已包含eventHubNameEntityPath两者不能同时指定cardinality: one每次调用只传一条消息需要手动修正见下consumerGroup: $Default使用的消费者组多个应用可以通过不同 consumer group 各自消费同一端点课程后续会展开连接串绝不能直接写进function.json只能通过设置项间接引用——这是为了防止连接串被意外泄露例如随代码提交到版本库。手动修正cardinality由于 Azure Functions 模板的一个已知缺陷模板生成的function.json中cardinality被错误地设为many需要手动改为onecardinality: one,将connection更新为指向刚添加的设置项connection: IOT_HUB_CONNECTION_STRING,注意它必须指向设置项名称而不是连接串本身。将eventHubName置为空串理由见上表。任务五启动触发器并验证确保上一课的 MQTT 事件监控程序没有运行。如果它同时连着 IoT Hub 消费同一组消息Functions 应用将连不上、处理不了事件不同 consumer group 可以并行消费但那是后续课程的主题。启动应用func start预期输出应用发现iot-hub-trigger函数并回放过去一天内已发送到 IoT Hub 的所有消息(.venv) ➜ soil-moisture-trigger func start Found Python version 3.9.1 (python3). Azure Functions Core Tools Core Tools Version: 3.0.3442 Commit hash: 6bfab24b2743f8421475d996402c398d2fe4a9e0 (64-bit) Function Runtime Version: 3.0.15417.0 Functions: iot-hub-trigger: eventHubTrigger For detailed output, run func with --verbose flag. [2021-05-05T02:44:07.517Z] Worker process started and initialized. [2021-05-05T02:44:09.202Z] Executing Functions.iot-hub-trigger (Reason(null), Id802803a5-eae9-4401-a1f4-176631456ce4) [2021-05-05T02:44:09.205Z] Trigger Details: PartitionId: 0, Offset: 1011240-1011632, EnqueueTimeUtc: 2021-05-04T19:04:04.2030000Z-2021-05-04T19:04:04.3900000Z, SequenceNumber: 2546-2547, Count: 2 [2021-05-05T02:44:09.352Z] Python EventHub trigger processed an event: {soil_moisture:628} [2021-05-05T02:44:09.354Z] Python EventHub trigger processed an event: {soil_moisture:624} [2021-05-05T02:44:09.395Z] Executed Functions.iot-hub-trigger (Succeeded, Id802803a5-eae9-4401-a1f4-176631456ce4, Duration245ms)每次函数调用都被Executing Functions.iot-hub-trigger/Executed Functions.iot-hub-trigger这对日志包裹从中可以看出单次调用处理了多少条消息、耗时多少。确认 IoT 设备在运行你会看到新的土壤湿度消息实时出现在 Functions 输出中。停止并重启应用它不会重新处理旧消息只处理新消息——因为触发器记住了上次消费的位点。调试技巧VS Code 支持给函数打断点点击行号左侧、F9切换断点然后按F5或选Run - Start debugging就能在事件处理过程中逐步查看数据。触发器故障排查错误现象排查方向The listener for function Functions.iot-hub-trigger was unable to start. ... Connection refused.Azurite 是否在运行local.settings.json中AzureWebJobsStorage是否为UseDevelopmentStoragetrueAttributeError: list object has no attribute get_bodyfunction.json的cardinality是否已改为one为many时event是列表The path to an Event Hub may be specified as part of the connection string or as a separate value, but not both. ...function.json中eventHubName是否已置为空串从 serverless 代码发送直接方法请求到这里Functions 应用只“听”不“说”。要控制继电器还需要从云端向设备发命令这要通过另一条连接——Registry Manager。Registry Manager 是管理 IoT Hub 注册表的工具查看已注册设备、发送云端到设备消息、调用直接方法、更新 device twin以及注册/更新/删除设备。同样需要一个连接串。任务六获取 Registry Manager 连接串执行连接串绑定到ServiceConnect策略——允许你的代码连接 IoT Hub 并向设备发消息az iot hub connection-string show --policy-name service \ --output table \ --hub-name hub_name在local.settings.json的Values段新增REGISTRY_MANAGER_CONNECTION_STRING: connection string任务七向设备发送直接方法请求requirements.txt添加azure-iot-hub仓库示例的 requirements.txt 即包含此依赖然后在激活了虚拟环境的终端执行pip install -r requirements.txt。重写iot-hub-trigger/__init__.py。仓库中完整实现见init.py与文档逐步给出的代码一致import logging import azure.functions as func import json import os from azure.iot.hub import IoTHubRegistryManager from azure.iot.hub.models import CloudToDeviceMethod def main(event: func.EventHubEvent): body json.loads(event.get_body().decode(utf-8)) device_id event.iothub_metadata[connection-device-id] logging.info(fReceived message: {body} from {device_id}) soil_moisture body[soil_moisture] if soil_moisture 450: direct_method CloudToDeviceMethod(method_namerelay_on, payload{}) else: direct_method CloudToDeviceMethod(method_namerelay_off, payload{}) logging.info(fSending direct method request for {direct_method.method_name} for device {device_id}) registry_manager_connection_string os.environ[REGISTRY_MANAGER_CONNECTION_STRING] registry_manager IoTHubRegistryManager(registry_manager_connection_string) registry_manager.invoke_device_method(device_id, direct_method) logging.info(Direct method request sent!)逐段解读解析消息event.get_body().decode(utf-8)取出设备发来的 JSON 遥测event.iothub_metadata[connection-device-id]从 IoT Hub 附加的标注中拿到发送方设备 ID标注还包括消息发送时间等。决策逻辑从消息里取soil_moisture超过 450 构造relay_on直接方法否则构造relay_off。直接方法请求不需要有效载荷所以 payload 是空 JSON 文档{}。读取连接串local.settings.json中的值在本地运行时以环境变量形式暴露通过os.environ读取。部署到云端后同样的键会变成 Function App 的Application Settings代码无需修改。发送请求registry_manager.invoke_device_method(device_id, direct_method)把直接方法请求发给发出这条遥测的那个设备。相比早期 MQTT 版本把继电器命令广播给所有设备隐含“只有一个设备”的假设这个版本天然支持多台传感器继电器并存能把正确的命令发给正确的设备。直接方法请求在 IoT Hub 中的消息流如下云端经 Registry Manager 向指定设备发起、设备端执行方法后返回结果启动 Functions 应用让设备持续上报。把土壤湿度传感器在土里/土外挪动观察湿度值变化、日志中Sending direct method request for relay_on/relay_off ...的输出以及继电器实际的开/关动作——此时整条“设备 → IoT Hub → serverless 函数 → 直接方法 → 设备继电器”的闭环已经全部运行在云端。将服务端代码部署到云端本地跑通后最后一步是部署。需要两块云资源Functions App 本身放入上一课为 IoT Hub 创建的 Resource Group以及一个真实的 Storage Account 替换本地的 Azurite 模拟器。任务八创建云资源创建 Storage Account名称全球唯一只能用小写字母和数字不超过 24 字符——因为它出现在访问 URL 中--sku Standard_LRS选最低的通用层价格档存储没有免费档、按量计费成本很低az storage account create --resource-group soil-moisture-sensor \ --sku Standard_LRS \ --name storage_name创建 Function Appaz functionapp create --resource-group soil-moisture-sensor \ --runtime python \ --functions-version 3 \ --os-type Linux \ --consumption-plan-location location \ --storage-account storage_name \ --name functions_app_name参数说明location与上一课创建 Resource Group 时相同的区域functions_app_name同样全球唯一的名称出现在访问 URL 中建议soil-moisture-sensor-加随机后缀--functions-version 3使用 v3 版 Azure Functions--os-type LinuxPython 函数应用只支持 Linux 托管函数也可部署在 Windows 上视语言而定--storage-account指定刚创建的存储账户。任务九上传应用设置本地开发时连接串放在local.settings.json而该文件只用于本地开发不应提交到版本库。云端对应物是Application Settings——托管在云端的键值对代码经环境变量读取运行时也用它把函数连到 IoT Hub。逐一写入az functionapp config appsettings set --resource-group soil-moisture-sensor \ --name functions_app_name \ --settings IOT_HUB_CONNECTION_STRINGconnection string再用同样命令写入REGISTRY_MANAGER_CONNECTION_STRING值来自local.settings.json中对应项。命令执行后会列出该 Function App 的全部 Application Settings可借此核对你会看到AzureWebJobsStorage已经存在——它在你创建 Function App 时传入 Storage Account 参数后就被自动设置了替代了本地文件里的UseDevelopmentStoragetrue。任务十发布 Functions App在soil-moisture-trigger目录执行func azure functionapp publish functions_app_name代码会被打包、上传并启动。控制台输出较多最终以部署确认和函数清单结尾本例只有一个触发器Deployment successful. Remote build succeeded! Syncing triggers... Functions in soil-moisture-sensor: iot-hub-trigger - [eventHubTrigger]让设备保持运行再改变土壤湿度调节真实湿度或把传感器挪出/插入土壤就能看到继电器随湿度变化自动开/关——与本地运行时行为一致但控制逻辑此刻运行在云端。延伸挑战与课后作业挑战题上一课你通过“继电器开启期间取消订阅 MQTT 消息、关闭后再短暂订阅”来控制继电器作用时间。serverless 下没有这个办法——你无法“退订” IoT Hub 触发器每条消息都会触发函数。请思考在 Functions App 中可以用哪些方式实现等价的延时控制提示方向结合 Application Settings / 外部存储记录设备状态与时间戳利用函数的“无本地状态”特性。课后作业Add manual relay controlserverless 代码可以被很多事物触发包括 HTTP 请求。作业要求给 Functions App 增加两个 HTTP 触发器如relay_on/relay_off实现从 Web 请求手动开关继电器的旁路控制完整说明见 assignment.md。关键要点用func new --name trigger name --template HTTP trigger添加触发器HTTP 触发器默认需要函数级 API key 才能调用作业允许将其设为匿名访问function.json中authLevel: anonymousHTTP 触发器默认支持 GET/POST本地运行时终端会打印形如http://localhost:7071/api/relay_on的 URL注意默认挂在api路由下直接粘贴到浏览器回车即可执行部署到云端后触发器 URL 形如https://functions app name.azurewebsites.net/api/trigger name。小结这一课完成了数字农业场景中的关键一跃控制逻辑从“跑在你电脑上的常驻 MQTT 进程”变成“事件驱动、按需计费、自动伸缩”的云端函数。整个迁移的落点只有四个文件——host.json应用配置、local.settings.json本地连接串、function.jsonEvent Hub 触发器绑定含cardinality: one与eventHubName: 两处易错点和init.py解析遥测、按阈值 450 通过IoTHubRegistryManager.invoke_device_method对单个设备下发relay_on/relay_off。理解这条“Event Hub 兼容端点进、Registry Manager 出”的双向链路后同样的模式可以复制到仓库中农场、运输、制造等所有后续项目的云端控制环节。【免费下载链接】IoT-For-Beginners12 Weeks, 24 Lessons, IoT for All!项目地址: https://gitcode.com/GitHub_Trending/io/IoT-For-Beginners创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
返回列表