IoT-For-Beginners 进阶实战:用 Twilio 为 Azure Functions 地理围栏触发器添加短信/邮件通知
2026/9/15 14:41:34 网站建设 项目流程

IoT-For-Beginners 进阶实战:用 Twilio 为 Azure Functions 地理围栏触发器添加短信/邮件通知

【免费下载链接】IoT-For-Beginners12 Weeks, 24 Lessons, IoT for All!项目地址: https://gitcode.com/GitHub_Trending/io/IoT-For-Beginners

导读

本文基于 IoT-For-Beginners 项目「运输」课程第 4 课(Geofences)的课后作业展开,讲解如何为已经具备地理围栏(Geofence)判定能力的 Azure Functions 应用接入第三方通信平台 Twilio,从而在 GPS 坐标进入或离开围栏时自动发送短信或邮件通知。读完本文,你将掌握 Azure Functions 输出绑定的配置方式、Twilio SMS 与 SendGrid 邮件两种绑定的取舍,以及如何在既有地理围栏代码基础上落地"仅当进入围栏时通知一次"的实战方案。

该作业的原始英文文本位于 3-transport/lessons/4-geofences/assignment.md,本课程完整讲义位于 3-transport/lessons/4-geofences/README.md,对应的可运行示例代码位于 3-transport/lessons/4-geofences/code/functions/gps-trigger。

背景:从"记录距离"到"主动通知"

在课程主体部分,你已经完成了一条完整的数据链路:GPS 传感器上报坐标 → IoT Hub 接收事件 →iot-hub-trigger函数把事件写入 Blob 存储 →geofence-trigger函数调用 Azure Maps 的 Spatial Get Geofence API 判断坐标与地理围栏的位置关系。

其中geofence-trigger的判定逻辑只做了日志输出。在仓库示例 geofence-trigger/init.py 中,核心判定代码是这样的:

distance = response_body['geometries'][0]['distance'] if distance == 999: logging.info('Point is outside geofence') elif distance > 0: logging.info(f'Point is just outside geofence by a distance of {distance}m') elif distance == -999: logging.info(f'Point is inside geofence') else: logging.info(f'Point is just inside geofence by a distance of {distance}m')

distance的语义来自 Azure Maps Geofence API:正数表示点在围栏外,负数表示在围栏内;当距离超过搜索缓冲区(默认 50m,可配置 0~500m)时返回999(外)或-999(内)。

本次作业的目标,就是把这个"只写日志"的环节升级为"主动通知":当 GPS 坐标位于地理围栏内部时,向相关人员发送一条短信或一封邮件,从而让卸货团队在卡车抵达处理中心前就做好准备。

前置知识:Azure Functions 的绑定(Bindings)机制

Azure Functions 采用声明式绑定(Bindings)来连接外部服务:触发器(Trigger)负责"进入函数"的数据源,输出绑定(Output Binding)负责"离开函数"的写入目标。绑定关系在函数的function.json中声明。

以仓库中的geofence-trigger为例,其 function.json 声明了事件触发器:

{ "scriptFile": "__init__.py", "bindings": [ { "type": "eventHubTrigger", "name": "events", "direction": "in", "eventHubName": "samples-workitems", "connection": "IOT_HUB_CONNECTION_STRING", "cardinality": "many", "consumerGroup": "geofence", "dataType": "binary" } ] }

要点说明:

  • type: eventHubTrigger表示该函数由 IoT Hub / Event Hub 事件触发;
  • consumerGroup: geofence指向你在本课新建的消费者组(区别于iot-hub-trigger使用的$Default,参见 iot-hub-trigger/function.json),保证两个触发器各自独立消费事件而不互相抢占;
  • cardinality: many表示一次可批量接收多条事件,对应 Python 代码中main(events: List[func.EventHubEvent])的签名。

作业要求的做法,是在此基础上追加一个输出绑定。Azure Functions 官方提供两类与 Twilio 相关的输出绑定:

  1. Twilio SMS 绑定:通过 Twilio 平台发送文本短信。适合通知对象在手机端、对实时性要求高的场景。
  2. Twilio SendGrid 绑定:通过 SendGrid(Twilio 旗下的邮件平台)发送电子邮件。适合需要携带较多上下文、留档追溯的场景。

两类绑定都属于"输出绑定",配置方式与普通存储/HTTP 输出绑定一致,差别仅在typename与连接字符串设置项上。

第一步:注册 Twilio 免费账户

  • 前往 Twilio 官方网站注册一个免费账户(作业原文指向 Twilio.com,注册入口即官网首页)。
  • 免费账户通常附带一个用于开发测试的额度(trial credit),足够完成本作业的短信或邮件发送验证。
  • 注册完成后,从 Twilio 控制台(Console)获取本账户的Account SIDAuth Token,它们会用于在 Azure Functions 中配置 Twilio 绑定的连接字符串。
  • 若选择短信方案,还需要在 Twilio 控制台完成一个Twilio 电话号码的申请/验证(免费账户默认只能向已验证的手机号发送短信),并在后续请求参数中作为发件人号码使用。

⚠️ 免费账户对发送对象有限制(通常只能发给已验证号码),正式环境需要升级账户并按量付费,本文以开发验证为目的描述。

第二步:阅读官方绑定文档,明确配置项

作业明确要求先阅读两份官方文档再动手:

  1. Azure Functions Twilio 绑定文档(Python 标签页):说明 SMS 输出绑定的function.json声明格式、name与连接设置的命名方式,以及函数代码中如何通过func.Out返回消息对象。
  2. Azure Functions SendGrid 绑定文档:说明邮件输出绑定的声明方式,以及from(发件人)、to(收件人)、subject(主题)、textContent/htmlContent(正文)等字段的填充方式。

两份文档的核心思想一致:绑定本身只负责"把消息对象交给服务",具体发送逻辑由平台完成,函数代码无需引入 Twilio SDK。这也正是"绑定"与"直接在代码里调用 SDK"的本质区别——绑定把第三方服务的连接细节从业务代码中剥离出去。

第三步:在 Functions 应用中添加通知绑定

结合仓库现有代码,推荐按以下步骤落地。以下以**短信(Twilio SMS)**方案为例,邮件方案会在文末给出等价配置差异。

3.1 修改function.json,追加输出绑定

geofence-trigger的 function.json 中,于现有bindings数组内追加一个对象:

{ "type": "twilioSms", "name": "message", "accountSidSetting": "TwilioAccountSid", "authTokenSetting": "TwilioAuthToken", "from": "+1XXXXXXXXXX", "direction": "out" }

字段说明:

字段含义取值建议
type绑定类型短信为twilioSms;邮件为sendGrid
name函数签名中的形参名本文示例为message
accountSidSetting存放 Account SID 的应用设置键名建议与下文local.settings.json键名一致
authTokenSetting存放 Auth Token 的应用设置键名同上
from发件人号码(Twilio 分配给您的号码)需替换为实际号码
direction方向输出绑定固定为out

邮件(SendGrid)方案的差异:type改为sendGrid,并把from换成发件邮箱;tosubject、正文内容通常在代码侧的对象字段中提供。

3.2 在local.settings.json中登记敏感信息

仓库中的 local.settings.json 是所有触发器共享的本地配置(注意其备注——local.settings.json在 Function App 内的所有触发器间共享),当前结构如下:

{ "IsEncrypted": false, "Values": { "FUNCTIONS_WORKER_RUNTIME": "python", "AzureWebJobsStorage": "UseDevelopmentStorage=true", "IOT_HUB_CONNECTION_STRING": "<connection string>", "STORAGE_CONNECTION_STRING": "<connection string>", "MAPS_KEY": "<subscription key>", "GEOFENCE_UDID": "<UDID>" } }

请按function.json中声明的键名追加:

"TwilioAccountSid": "<your Twilio Account SID>", "TwilioAuthToken": "<your Twilio Auth Token>"

⚠️local.settings.json仅用于本地调试,其中的连接字符串与密钥不应提交到版本库;部署到云端时,需要通过az functionapp config appsettings set之类的命令把同样的键值上传为 Azure 应用设置(Application Settings),详见 2-farm 课程第 5 课 部署与应用设置上传指引。

3.3 修改__init__.py:仅在围栏内发送通知

作业有一个关键约束:在"坐标位于围栏内"和"坐标位于围栏外"两种状态中,只通知其中一种,不能都发。仓库示例geofence-trigger/__init__.py的判定分支(distance == 999/distance > 0/distance == -999/ 其他)天然适合改造——你只需在选定的分支中把日志替换为消息发送即可。

以"仅当点位于围栏内时发送短信"为例,在main函数中把message形参(与function.jsonname对应)声明为func.Out[str],并在进入围栏的分支中写入消息:

from typing import List import logging import json import os import requests import azure.functions as func def main(events: List[func.EventHubEvent], message: func.Out[str]): maps_key = os.environ['MAPS_KEY'] geofence_udid = os.environ['GEOFENCE_UDID'] for event in events: event_body = json.loads(event.get_body().decode('utf-8')) lat = event_body['gps']['lat'] lon = event_body['gps']['lon'] url = 'https://atlas.microsoft.com/spatial/geofence/json' params = { 'api-version': 1.0, 'deviceId': 'gps-sensor', 'subscription-key': maps_key, 'udid': geofence_udid, 'lat': lat, 'lon': lon } response = requests.get(url, params=params) response_body = json.loads(response.text) distance = response_body['geometries'][0]['distance'] if distance == -999 or distance < 0: # 点位于地理围栏内部(或处于搜索缓冲区内侧)→ 发送通知 message.set(f'Truck is inside the geofence') logging.info('Notification sent: point is inside geofence') else: # 点在围栏外 → 只记录日志,不发送通知 logging.info(f'Point is outside geofence, distance = {distance}m')

代码要点:

  • message.set(...)是输出绑定在 Python 中的写入方式,函数返回后平台负责把字符串内容交给 Twilio 发送;
  • 判定条件distance == -999 or distance < 0覆盖"深在围栏内"(-999)与"距围栏边界 50m 缓冲区内侧"两种情况,与仓库原代码中"围栏内/外"的判定口径保持一致;
  • 若选择邮件方案,可将message的类型改为func.Out[func.SendGridMessage],并填充tosubjectcontent等字段,其余结构不变。

3.4 本地运行与云端部署

  • 本地运行func start(或按项目惯例使用 VS Code + Azure Functions 扩展),用虚拟设备或手机热点改变 GPS 坐标,观察是否仅在进入围栏时收到短信/邮件。
  • 确认无误后,把修改后的function.json__init__.py部署到云端的 Function App。注意同步上传新增的TwilioAccountSidTwilioAuthToken应用设置,否则云端运行时会因找不到环境变量而报错。
  • 若本地与云端同时运行且都订阅了同一消费者组,会产生重复处理;可参照课程正文的做法,用az functionapp stop临时停掉云端实例,避免干扰验证。

第四步:按评估标准自测

作业附带的评分表(Rubric)如下,可作为验收清单:

标准优秀(Exemplary)合格(Adequate)待改进(Needs Improvement)
配置函数绑定并收到邮件或短信能配置函数绑定,并在坐标位于围栏内或外其中一种状态时收到邮件或短信能配置绑定但未能发出邮件或短信;或坐标在围栏内外两种状态都会触发发送未能配置绑定,也未能发出邮件或短信

自测要点:

  1. 绑定是否生效:进入围栏能否收到通知?
  2. 是否只发一种状态:离开围栏时不应再次收到通知(排除"内外都发"的情况);
  3. 配置完整性function.jsonlocal.settings.json、云端应用设置三处配置是否一致;
  4. 密钥安全:Token 类敏感信息是否只存在于应用设置中,而非硬编码在源码里。

常见问题与排错思路

  • 收到BadRequest/ 绑定初始化失败:优先检查function.jsonaccountSidSettingauthTokenSetting指定的键名是否与local.settings.json/ 应用设置中的键名完全一致。
  • 短信发不出去:确认 Twilio 号码已完成验证、接收方手机号在免费账户的白名单内;确认from使用 Twilio 分配给账户的号码(含国家码)。
  • 邮件发不出去:确认 SendGrid 发件人(Sender)已完成域名/邮箱验证,且to地址合法。
  • 内外都收到了通知:检查判定分支是否把两个方向都写了message.set(...),作业明确要求只通知一种状态。
  • 部署后本地正常、云端不触发:检查云端 Application Settings 是否已上传新增的 Twilio 配置,并确认consumerGroup: geofence在云端同样存在(该消费者组由az iot hub consumer-group create创建,与本地环境无关,云端直接沿用)。

延伸:从单围栏到多围栏

课程正文的挑战环节(Challenge)指出:FeatureCollection中可以包含多个带不同geometryId的多边形,只要在properties.geometryId上保持唯一即可一次上传。若你在作业基础上继续扩展,可以上传多围栏 GeoJSON,然后遍历response_body['geometries'](而不是固定取[0]),找出距离最近或命中的那个围栏再决定是否通知——这与仓库示例中distance = response_body['geometries'][0]['distance']的单围栏假设形成对照,也是把"通知"做得更贴合真实业务(如区分厂区、卸货区、禁行区)的自然演进方向。

总结

本作业把课程中"判断 GPS 坐标是否在围栏内"的日志逻辑升级为"进入围栏即自动通知"的闭环能力:通过 Azure Functions 的声明式输出绑定接入 Twilio,短信与邮件二选一,且严格限定只在一种状态触发。结合仓库中 geofence-trigger 的现有代码,你可以在不改动地理围栏判定核心的前提下,用最小代码量获得"卡车即将抵达"的实时告警——这正是课程所在供应链场景中"提前组织卸货人力、减少车辆等待"的关键一步。

【免费下载链接】IoT-For-Beginners12 Weeks, 24 Lessons, IoT for All!项目地址: https://gitcode.com/GitHub_Trending/io/IoT-For-Beginners

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询