IoT-For-Beginners 项目实践在无服务器代码中处理取消定时器意图并下发命令【免费下载链接】IoT-For-Beginners12 Weeks, 24 Lessons, IoT for All!项目地址: https://gitcode.com/GitHub_Trending/io/IoT-For-Beginners导读本篇文章围绕 IoT-For-Beginners 开源课程中 Spoken feedback语音反馈一课的项目作业展开在上一课中你已经在 LUISLanguage Understanding Intelligent Service应用中为智能定时器添加了 cancel timer取消定时器意图而本作业要求你把它真正落地——在 Azure Functions 无服务器代码中识别该意图、向 IoT 设备下发命令并在设备端完成定时器的取消。阅读并动手完成后你将掌握意图识别 → 无服务器业务逻辑 → 设备命令下发 → 设备端动作这条完整的语音交互闭环并能通过仓库中的完整源码验证每一步的实现细节。作业背景从设置定时器到取消定时器本作业对应 6-consumer/lessons/3-spoken-feedback/assignment.md原文为英文 Cancel the timer本仓库同时提供保加利亚语等翻译版本。它是课程 设置定时器并提供语音反馈 一课的收尾任务延续了前面两课的成果第 1 课语音识别把用户说的话转成文本第 2 课语言理解把文本交给 LUIS识别出set timer意图并抽取number、time unit等实体第 3 课语音反馈设备根据返回的秒数设置定时器用 TTS 播报开始与结束本作业新增对cancel timer意图的处理让用户能在定时器结束前主动取消它。作业原文给出的使用场景非常贴近生活也许你的面包已经烤好了可以在定时器响之前把它从烤箱里拿出来。这正是取消定时器这一功能的现实价值——语音助手的交互不应该只有单向的设置还应该支持随时撤销。上一课的铺垫在 LUIS 中定义取消意图作业的第一步已在 6-consumer/lessons/2-language-understanding/assignment.md 中完成是在 LUIS 应用中新增一个cancel timer意图不需要任何实体为它提供若干示例语句如 Cancel the timer、Stop the timer、Remove the timer 等训练并发布模型在无服务器代码中检测该意图是否为top_intent置信度最高的意图记录日志并返回合适的响应。本作业正是上述工作的延续让 cancel timer 意图真正产生设备端效果。任务总览三个环节的完整链路根据作业的 Instructions本任务包含三个递进环节环节目标1. 处理意图在无服务器代码Azure Functions中识别cancel timer为顶层意图2. 发送命令从无服务器代码向 IoT 设备下发取消定时器的命令3. 取消定时器IoT 设备收到命令后真正取消正在运行的定时器作业同时给出了严格的评分标准Rubric这是验收的自检清单也是下文逐环节展开的主线标准优秀Exemplary合格Adequate待改进Needs Improvement在无服务器代码中处理意图并发送命令成功处理意图并向设备发送了命令成功处理意图但未能向设备发送命令未能处理意图在设备上取消定时器成功收到命令并取消定时器成功收到命令但未能取消定时器未能收到命令可以看到这条评分标准把意图处理与设备动作严格分开验收。即使无服务器端识别得再好只要设备端没有取消定时器就只是合格而非优秀。因此在动手时务必同时关注链路的两端。环节一无服务器代码中的意图处理课程的无服务器部分基于 Azure Functions 的 HTTP 触发器实现。仓库中给出了两个可对照的版本第 2 课仅设置定时器的基准实现6-consumer/lessons/2-language-understanding/code/functions/smart-timer-trigger/text-to-timer/init.py第 3 课含语音反馈的完整实现6-consumer/lessons/3-spoken-feedback/code-spoken-response/functions/smart-timer-trigger/text-to-timer/init.py读取环境变量与构造 LUIS 客户端处理函数首先从环境变量读取 LUIS 的凭据与配置并基于 Azure SDK 构造运行时客户端def main(req: func.HttpRequest) - func.HttpResponse: luis_key os.environ[LUIS_KEY] endpoint_url os.environ[LUIS_ENDPOINT_URL] app_id os.environ[LUIS_APP_ID] credentials CognitiveServicesCredentials(luis_key) client LUISRuntimeClient(endpointendpoint_url, credentialscredentials)这些环境变量LUIS_KEY、LUIS_ENDPOINT_URL、LUIS_APP_ID在 Azure Functions 的 local.settings.json 中配置。从源码结构看课程要求开发者在本地调试时把密钥放入该文件的Values段部署到云端后则改为 Function App 的应用设置。解析请求并调用 LUIS 预测函数接收的 HTTP 请求体是一个 JSON其中包含语音转文本得到的句子req_body req.get_json() text req_body[text] logging.info(fRequest - {text}) prediction_request { query : text } prediction_response client.prediction.get_slot_prediction(app_id, Staging, prediction_request)注意这里调用的是get_slot_prediction且发布槽位slot为Staging——这意味着 LUIS 应用必须已发布到 Staging暂存槽位代码才能命中预测。这是排查意图识别不到类问题时首先应检查的点。按顶层意图分发逻辑本作业的核心改造点基准实现只针对set timer意图做了处理if prediction_response.prediction.top_intent set timer: numbers prediction_response.prediction.entities[number] time_units prediction_response.prediction.entities[time unit] total_seconds 0 for i in range(0, len(numbers)): number numbers[i] time_unit time_units[i][0] if time_unit minute: total_seconds number * 60 else: total_seconds number logging.info(fTimer required for {total_seconds} seconds) payload { seconds: total_seconds } return func.HttpResponse(json.dumps(payload), status_code200) return func.HttpResponse(status_code404)代码会把number实体与time unit实体按下标一一配对minute乘以 60 折算成秒最终以 JSON{seconds: N}返回状态码 200如果顶层意图不是set timer则返回 404。本作业要求你在if分发处扩展第二个分支例如if prediction_response.prediction.top_intent set timer: # ...原有秒数计算逻辑... return func.HttpResponse(json.dumps({seconds: total_seconds}), status_code200) elif prediction_response.prediction.top_intent cancel timer: logging.info(Cancel timer intent detected) # 向 IoT 设备下发取消命令见环节二 return func.HttpResponse(json.dumps({command: cancel timer}), status_code200) return func.HttpResponse(status_code404)这样无论用户说 Set a 3 minute timer 还是 Cancel the timer无服务器代码都能依据top_intent走对分支并把结果以 HTTP 响应返回给设备端。HTTP 触发器本身的绑定定义在 function.json 中同时支持get与post两种方法鉴权级别为function。环节二把命令下发给 IoT 设备无服务器代码与设备之间的通信在课程设计中采用设备主动轮询 REST 端点的方式设备把语音文本 POST 到 Function 的 HTTP 地址函数返回秒数或命令。设备端的调用逻辑可以对照仓库中 Python 版虚拟设备 / Raspberry Pi 的实现 code-spoken-response/virtual-iot-device/smart-timer/app.pydef get_timer_time(text): url URL body { text: text } response requests.post(url, jsonbody) if response.status_code ! 200: return 0 payload response.json() return payload[seconds]要点把语音识别得到的text作为 JSON 的text字段 POST 出去只有状态码为 200 时才解析返回体非 200 一律返回 0避免把错误当成功处理在课程的操作指南 single-board-computer-set-timer.md 中这一步被拆解为定义 URL → 构造 body → POST 请求 → 校验状态码并解析 payload四个小步骤你可以按此顺序逐步实现。针对本作业设备端在拿到响应后需要区分两种结果响应里是seconds设置定时器还是command取消定时器。可以推断作业要求你对process_text做类似这样的扩展def process_text(text): print(text) seconds get_timer_time(text) if seconds 0: create_timer(seconds) else: # 检查响应中是否包含取消命令若包含则 cancel_timer() pass实际命令下发的通道并不局限于此——如果你的实现把 Function 接入 Azure IoT Hub也可以用云到设备C2D消息或直接方法direct method下发命令课程仓库的 docs 与第 2 课内容中有 IoT Hub 消息机制的示意见 images/iot-hub-cloud-to-device-message.png。本作业采用哪种通道均可关键判定标准是设备端确实收到了命令。环节三在设备端取消定时器这是评分表中第二行标准的落点。不同硬件平台的定时器实现不同仓库为三种目标设备都提供了源码Python 设备虚拟 IoT 设备 / Raspberry PiPython 版本使用标准库threading.Timer实现定时器见 code-spoken-response/virtual-iot-device/smart-timer/app.pydef create_timer(total_seconds): minutes, seconds divmod(total_seconds, 60) threading.Timer(total_seconds, announce_timer, args[minutes, seconds]).start() announcement if minutes 0: announcement f{minutes} minute if seconds 0: announcement f{seconds} second announcement timer started. say(announcement)threading.Timer(delay, function, args)会在delay秒后于后台线程执行functiondivmod(total_seconds, 60)把总秒数拆成分 余秒用于播报。课程指南 single-board-computer-set-timer.md 还演示了运行效果piraspberrypi:~/smart-timer $ python3 app.py Set a two minute 27 second timer. 2 minute 27 second timer started. Times up on your 2 minute 27 second timer.取消实现要点要取消一个threading.Timer必须在启动时保存返回的Timer对象例如保存为全局变量current_timer随后调用current_timer.cancel()。当响应指示 cancel timer 意图时执行if current_timer is not None: current_timer.cancel() current_timer None注意Timer.cancel()只在定时器尚未触发时有效如果定时器线程已经在执行announce_timer则需要额外的同步/标记机制来避免重复播报——这是把评分从合格推向优秀的细节。Wio TerminalArduinoWio Terminal 版本基于arduino-timer库核心代码在 code-spoken-response/wio-terminal/smart-timer/src/main.cppauto timer timer_create_default(); bool timerExpired(void *announcement) { say((char *)announcement); return false; } // 在 processAudio() 中 int total_seconds languageUnderstanding.GetTimerDuration(text); if (total_seconds 0) { return; } // ...拼装 begin_message / end_message ... say(begin_message); timer.in(total_seconds * 1000, timerExpired, (void *)(end_message.c_str()));timer.in(delay_ms, callback, arg)会在指定毫秒数后触发回调主循环中的timer.tick()位于loop()末尾负责驱动定时器内部状态。课程把GetTimerDuration等逻辑封装在language_understanding.h头文件中主程序只需调用并判断返回值。取消实现要点arduino-timer的timer.in()返回一个定时器 ID调用timer.cancel(id)即可在到期前取消。因此取消意图的分支大致为// 保存定时器 ID 到全局变量 int timer_id timer.in(total_seconds * 1000, timerExpired, (void *)(end_message.c_str())); // 收到取消命令时 timer.cancel(timer_id);两个平台的共同原则是一致的定时器的句柄/引用必须在创建时被保留取消操作才能命中它。取消后的语音反馈结合本课提供语音反馈的主题README取消定时器后设备还可以用 TTS 播报一句确认语例如 Timer cancelled。仓库中say/textToSpeech的调用链已经就绪——Python 端使用 Azure Speech SDK 的SpeechSynthesizer.speak_ssml(ssml)见 app.pyWio Terminal 端则调用textToSpeech.convertTextToSpeech(text)SSML 模板形如speak version1.0 xml:langen-GB voice xml:langen-GB nameen-GB-MiaNeural Your 3 minute 5 second time has been set /voice /speak把这套现成的播报机制复用到取消成功的确认上整个交互闭环就完整了听 → 懂 → 做 → 说。验收与自检对照评分标准逐条核对完成实现后请按作业的评分表做最终验收无服务器端对 Cancel the timer 之类的语句top_intent是否命中cancel timer是否记录日志并返回了正确的 HTTP 状态码命令下发设备端是否成功请求到响应并从中识别出取消命令而非把命令当秒数解析设备动作定时器是否真正被取消——运行一个较长定时器在到期前发出取消指令观察是否不再出现 Times up on your ... timer 的播报边界情况没有运行中的定时器时收到取消命令程序是否不会崩溃空引用/空句柄处理定时器恰好已触发时才收到命令是否避免了重复播报此外整个链路还依赖若干前置条件LUIS 应用已发布到Staging槽位、Function App 的环境变量LUIS_KEY、LUIS_ENDPOINT_URL、LUIS_APP_ID、SPEECH_KEY、SPEECH_LOCATION已正确配置。任一项缺失都会导致链路在对应环节静默失败排查时可以先用curl直接 POST 一段文本到 Function 端点确认无服务器端行为正常再回到设备端排查。小结本作业用三个环节把取消定时器这一语音交互功能完整打通无服务器代码依据 LUIS 的top_intent分发到cancel timer分支并下发命令设备端保存定时器句柄并在收到命令后取消。仓库中的 text-to-timer 函数、Python 设备端 与 Wio Terminal 设备端 分别给出了各平台的可运行参考实现你可以在此基础上直接增量扩展取消逻辑。完成它你就掌握了语音助手设置 撤销双向交互的完整工程模式——这同样是真实智能音箱产品中定时器、闹钟、提醒类功能的核心骨架。【免费下载链接】IoT-For-Beginners12 Weeks, 24 Lessons, IoT for All!项目地址: https://gitcode.com/GitHub_Trending/io/IoT-For-Beginners创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
