IoT-For-Beginners 智能定时器多语言实战:在 Wio Terminal 上集成 Azure Translator 实现语音翻译
IoT-For-Beginners 智能定时器多语言实战在 Wio Terminal 上集成 Azure Translator 实现语音翻译【免费下载链接】IoT-For-Beginners12 Weeks, 24 Lessons, IoT for All!项目地址: https://gitcode.com/GitHub_Trending/io/IoT-For-Beginners本篇技术指南基于开源课程 IoT-For-Beginners 第 6 部分「消费级 IoT」第 4 课多语言支持的 Wio Terminal 实践章节完整讲解如何在智能语音定时器中接入 Azure Translator 翻译服务先用 Azure Functions 将翻译 REST API 封装为translate-textHTTP 触发器再在 Wio Terminal 端实现TextTranslator类把用户语音与定时器响应在「用户语言」与「服务端语言」之间双向翻译。读完本文你将掌握从云函数封装到嵌入式端 HTTP 调用、再到与既有语音识别/合成流程无缝衔接的完整技术链路可直接复现一个支持法语/英语等多种语言交互的语音定时器。为什么需要翻译层语音服务 REST API 并不直接支持翻译在 IoT-For-Beginners 课程第 6 部分前几课中智能定时器已经具备了语音转文本Speech to Text、语言理解LUIS和文本转语音Text to Speech能力但整套流程都限定在单一语言内。要让定时器支持多语言用户交互最直接的思路是引入翻译能力。这里有一个关键架构约束语音服务Azure Speech Service的 REST API 不支持直接翻译只有 Speech SDK 才内置了语音识别时的多语言翻译能力。因此课程采用了一种更轻量、更易嵌入的桥接方案使用独立的Azure Translator认知服务 Translator来处理文本翻译将 Translator 的 REST API 封装在函数应用Functions App的一个新 HTTP 触发器translate-text中供 Wio Terminal 通过 HTTP POST 简单调用。整个多语言方案的核心思路见课程架构图多语言智能定时器翻译架构。以「日语 → 英语」为例用户用日语说出指令 → 语音转文本得到日文 → 翻译成英文 → 交给 LUIS 与定时器核心逻辑保持英文不变→ 生成英文响应文本 → 翻译回日文 → 文本转语音播放。这样只需在两端各加一层翻译应用核心逻辑完全不动就能快速支持新语言。课程还指出这种机器翻译方案有一个已知代价不同语言和文化对同一事物有不同的表达方式翻译结果可能与你预期的措辞不完全一致且翻译并不总是对称的——同一句话翻译过去再翻译回来结果可能有细微差别。因此当你不精通多种语言时可以先用 Bing Translate 或 Google Translate 之类的在线翻译工具把要说的句子翻译成目标语言再借助「聆听翻译」Listen translation按钮把翻译后的音频对着麦克风说出来。下图所示即为 Bing Translate 中的「聆听翻译」按钮这也是本课原文的示意配图下面进入实战环节。整个实践分为三个任务创建 serverless 翻译函数 → 在 Wio Terminal 上编写翻译类 → 将翻译接入识别与响应流程。任务一创建翻译文本的 Serverless 函数翻译函数的职责非常单一接收一段文本与源/目标语言代码调用 Translator REST API返回翻译结果。它被实现为函数应用smart-timer-trigger中的一个新 HTTP 触发器仓库中对应的完整实现位于 translate-text/init.py。第一步在 local.settings.json 中配置翻译服务凭据在 VS Code 中打开smart-timer-trigger项目确认终端里的虚拟环境已激活若未激活杀掉终端重新创建。然后打开local.settings.json在Values中追加翻译器的 API 密钥与区域设置TRANSLATOR_KEY: key, TRANSLATOR_LOCATION: locationkey替换为你的 Translator 服务资源的 API 密钥location替换为你创建 Translator 服务资源时使用的区域location。从仓库的 local.settings.json 可以看到这个函数应用最终会同时持有 LUIS、语音与翻译三类凭据它们都通过环境变量在__init__.py中读取{ IsEncrypted: false, Values: { FUNCTIONS_WORKER_RUNTIME: python, AzureWebJobsStorage: , LUIS_KEY: primary key, LUIS_ENDPOINT_URL: endpoint url, LUIS_APP_ID: app id, SPEECH_KEY: key, SPEECH_LOCATION: location, TRANSLATOR_KEY: key, TRANSLATOR_LOCATION: location } }注意requirements.txt查看中只声明了azure-functions、azure-cognitiveservices-language-luis和librosa翻译函数用到的requests是 Python 标准第三方库随 Azure Functions Python 运行时环境可用无需额外添加。第二步用 func CLI 创建 HTTP 触发器在 VS Code 终端中定位到函数应用项目的根目录执行func new --name translate-text --template HTTP trigger该命令会生成名为translate-text的 HTTP 触发器目录。仓库中对应目录的绑定定义见 function.json它声明了一个httpTrigger类型的输入绑定允许get和post两种方法authLevel为function函数级鉴权输出绑定为$return直接返回 HTTP 响应。第三步编写init.py 调用 Translator REST API将translate-text目录下__init__.py的内容替换为如下实现与仓库源码一致import logging import os import requests import azure.functions as func location os.environ[TRANSLATOR_LOCATION] translator_key os.environ[TRANSLATOR_KEY] def main(req: func.HttpRequest) - func.HttpResponse: req_body req.get_json() from_language req_body[from_language] to_language req_body[to_language] text req_body[text] logging.info(fTranslating {text} from {from_language} to {to_language}) url fhttps://api.cognitive.microsofttranslator.com/translate?api-version3.0 headers { Ocp-Apim-Subscription-Key: translator_key, Ocp-Apim-Subscription-Region: location, Content-type: application/json } params { from: from_language, to: to_language } body [{ text : text }] response requests.post(url, headersheaders, paramsparams, jsonbody) return func.HttpResponse(response.json()[0][translations][0][text])这段代码的逻辑可以拆解为四步读取凭据模块级从环境变量读取TRANSLATOR_LOCATION与TRANSLATOR_KEY。从源码结构看这两个环境变量由local.settings.json的Values注入解析请求体req.get_json()提取from_language、to_language和text三个字段分别代表源语言、目标语言和待翻译文本构造并发送请求调用api.cognitive.microsofttranslator.com/translate?api-version3.0。源/目标语言作为 URL 参数params传入待翻译文本以 JSON 数组形式放在请求体body中鉴权通过两个请求头完成——Ocp-Apim-Subscription-Key携带密钥Ocp-Apim-Subscription-Region携带区域提取翻译结果Translator v3 的响应结构是[{translations: [{text: ...}]}]因此取response.json()[0][translations][0][text]作为响应文本直接返回。关于区域头Ocp-Apim-Subscription-Region当 Translator 资源部署在非全球区域时请求必须带上创建资源时使用的区域这一点与语音服务的使用方式一致。若缺失或填错API 会返回鉴权失败。第四步本地运行并用 curl 验证在 VS Code 终端中启动函数应用本地开发端口默认为7071然后像测试之前课次中的text-to-timer触发器一样用 curl 类工具调用。请求必须携带 JSON 请求体包含待翻译文本与两种语言{ text: Définir une minuterie de 30 secondes, from_language: fr-FR, to_language: en-US }这个示例把法语Définir une minuterie de 30 secondes设置一个 30 秒的定时器翻译为美式英语预期返回Set a 30-second timer。对应的 HTTP 调用形式可参考仓库中config.h的 URL 模式http://IP_ADDRESS:7071/api/translate-text见下文本地调试时以 POST 方式访问该地址并带上上述 JSON 即可。任务二在 Wio Terminal 上使用翻译函数翻译文本完成云端的translate-text函数后接下来把翻译能力搬到 Wio Terminal 的smart-timer工程中。仓库中的完整 C 工程位于 code/wio-terminal/smart-timer其中与本课相关的文件是 config.h、text_translator.h 和 main.cpp。第一步在 config.h 中声明两种语言与翻译凭据智能定时器现在会涉及两种语言用户语言用户实际使用并说话的语种服务端语言训练 LUIS 时使用的语种同时用于构建向用户播报的提示文本。因此需要修改config.h将LANGUAGE常量更新为用户语言并新增SERVER_LANGUAGE常量const char *LANGUAGE user language; const char *SERVER_LANGUAGE server language;user language替换为你将使用的语言区域名locale例如法语用fr-FR粤语用zn-HKserver language替换为训练 LUIS 所用语言的区域名。以仓库源码 config.h 为准这两个常量紧随SPEECH_API_KEY、SPEECH_LOCATION之后声明const char *LANGUAGE LANGUAGE; const char *SERVER_LANGUAGE LANGUAGE;接下来在SPEECH_LOCATION下方追加翻译服务的 API 密钥与区域const char *TRANSLATOR_API_KEY KEY; const char *TRANSLATOR_LOCATION LOCATION;KEY替换为翻译服务资源的 API 密钥LOCATION替换为创建翻译服务资源时使用的区域。最后在VOICE_URL下方追加翻译触发器的 URLconst char *TRANSLATE_FUNCTION_URL URL;URL替换为函数应用中translate-textHTTP 触发器的地址。它的格式与TEXT_TO_TIMER_FUNCTION_URL相同只是函数名从text-to-timer换成了translate-text。仓库源码中的实际声明为const char *TRANSLATE_FUNCTION_URL http://IP_ADDRESS:7071/api/translate-text;关于TRANSLATOR_API_KEY的说明从源码结构看config.h 中虽然声明了TRANSLATOR_API_KEY但当前版本的text_translator.h并未直接使用它——翻译请求的鉴权完全由云端函数通过环境变量TRANSLATOR_KEY完成Wio Terminal 只负责把文本与语言代码 POST 给translate-text函数。保留该常量是为了与课程讲解的资源创建步骤保持一致也便于未来若改为直连 Translator API 时使用。第二步新建 text_translator.h 翻译类在src目录下新建文件text_translator.h其中将包含一个负责翻译文本的类。首先声明类的骨架并创建一个全局实例#pragma once #include Arduino.h #include ArduinoJson.h #include HTTPClient.h #include WiFiClient.h #include config.h class TextTranslator { public: private: WiFiClient _client; }; TextTranslator textTranslator;要点#pragma once防止重复包含引入ArduinoJson.h用于构造 JSON 请求体、HTTPClient.h与WiFiClient.h用于发起 HTTP 请求类持有唯一的私有字段_clientWiFi 客户端并为HTTPClient提供底层连接文件末尾定义了全局实例textTranslator与工程中speechToText、textToSpeech等对象的命名风格保持一致。然后在public段添加翻译方法String translateText(String text, String from_language, String to_language) { }该方法接收三个参数待翻译文本、源语言、目标语言。它在两处被调用方向相反处理语音时把用户语音从用户语言翻译成LUIS 服务端语言生成响应时把播报文本从LUIS 服务端语言翻译回用户语言。接下来在方法体内逐步填充实现。第一步构造包含文本与语言的 JSON 请求体DynamicJsonDocument doc(1024); doc[text] text; doc[from_language] from_language; doc[to_language] to_language; String body; serializeJson(doc, body); Serial.print(Translating ); Serial.print(text); Serial.print( from ); Serial.print(from_language); Serial.print( to ); Serial.print(to_language);DynamicJsonDocument doc(1024)为文档分配 1024 字节缓冲——本课翻译的短句如定时器指令远小于该容量三个键text/from_language/to_language与云端__init__.py中req_body读取的字段严格对应serializeJson(doc, body)把文档序列化为 JSON 字符串串口日志先行打印翻译动作便于串口监视器排查。第二步把请求体发送给 serverless 函数应用HTTPClient httpClient; httpClient.begin(_client, TRANSLATE_FUNCTION_URL); int httpResponseCode httpClient.POST(body);httpClient.begin(_client, TRANSLATE_FUNCTION_URL)让 HTTP 客户端复用类中的 WiFi 连接并指向translate-text触发器httpClient.POST(body)以 POST 方式发送 JSON 字符串并返回 HTTP 状态码。第三步读取响应String translated_text ; if (httpResponseCode 200) { translated_text httpClient.getString(); Serial.print(Translated: ); Serial.println(translated_text); } else { Serial.print(Failed to translate text - error ); Serial.println(httpResponseCode); }因为云端函数直接以纯文本翻译结果作为响应体返回所以状态码为 200 时用httpClient.getString()取回的就是翻译文本非 200 时打印错误码便于诊断。第四步关闭连接并返回结果httpClient.end(); return translated_text;httpClient.end()释放 HTTP 连接资源随后把翻译结果可能为空字符串返回给调用方。仓库中该文件的完整实现见 text_translator.h其内容与上述步骤拼接后的结果完全一致。任务三翻译识别出的语音与响应文本翻译类就绪后最后一步是把它接入main.cpp的既有语音处理流程。引入头文件打开main.cpp在文件顶部为TextTranslator类头文件添加 include 指令。仓库源码 main.cpp 中该指令位于text_to_speech.h之后#include text_translator.h在 say 函数中翻译播报文本定时器被设置或到时后需要把播报文本翻译成用户语言。在say函数的第一行加入text textTranslator.translateText(text, LANGUAGE, SERVER_LANGUAGE);从仓库源码看say函数现在的完整形态是void say(String text) { text textTranslator.translateText(text, SERVER_LANGUAGE, LANGUAGE); Serial.println(text); textToSpeech.convertTextToSpeech(text); }注意参数顺序say收到的文本由核心逻辑以服务端语言生成因此是从SERVER_LANGUAGE翻译到LANGUAGE用户语言翻译结果再交给textToSpeech.convertTextToSpeech合成语音。在 processAudio 中翻译识别出的语音在processAudio函数中语音先通过String text speechToText.convertSpeechToText();转为文本随后立即翻译String text speechToText.convertSpeechToText(); text textTranslator.translateText(text, LANGUAGE, SERVER_LANGUAGE);这次的方向与say相反把用户语言LANGUAGE翻译成服务端语言SERVER_LANGUAGE确保后续languageUnderstanding.GetTimerDuration(text)能用 LUIS 训练时使用的语言解析出定时器时长。仓库中processAudio的对应实现见 main.cpp。编译上传与串口验证本项目使用 PlatformIO 构建Wio Terminal 的工程配置见 platformio.ini平台为atmelsam板卡为seeed_wio_terminal框架为 Arduino依赖库包括Seeed Arduino rpcWiFi、Seeed_Arduino_mbedtls提供 TLS 支持、ArduinoJson 6.17.3以及arduino-timer 2.3.0用于定时器回调等。构建并上传到 Wio Terminal 后通过串口监视器测试。串口出现Ready后按下 C 按钮左侧、最靠近电源开关的那个按钮并开始说话。请确保函数应用正在运行并尝试用用户语言发出定时器请求——可以自己说该语言也可以用翻译应用播放翻译后的音频。课程文档给出的典型串口输出如下Connecting to WiFi.. Connected! Got access token. Ready. Starting recording... Finished recording Sending speech... Speech sent! {RecognitionStatus:Success,DisplayText:Définir une minuterie de 2 minutes 27 secondes.,Offset:9600000,Duration:40400000} Translating Définir une minuterie de 2 minutes 27 secondes. from fr-FR to en-US Translated: Set a timer of 2 minutes 27 seconds. Set a timer of 2 minutes 27 seconds. {seconds: 147} Translating 2 minute 27 second timer started. from en-US to fr-FR Translated: 2 minute 27 seconde minute a commencé. 2 minute 27 seconde minute a commencé. Translating Times up on your 2 minute 27 second timer. from en-US to fr-FR Translated: Chronométrant votre minuterie de 2 minutes 27 secondes. Chronométrant votre minuterie de 2 minutes 27 secondes.对这段输出可以按时间线解读连接与就绪Connecting to WiFi..→Connected!→Got access token.→Ready.设备完成 WiFi 连接、语音服务令牌获取与各模块初始化语音识别按下 C 键后录音 →Sending speech...→Speech sent!返回的 JSON 显示识别成功RecognitionStatus:SuccessDisplayText为法语Définir une minuterie de 2 minutes 27 secondes.输入方向翻译Translating ... from fr-FR to en-US→Translated: Set a timer of 2 minutes 27 seconds.用户语言法语被翻译为服务端语言英语随后 LUIS 解析出{seconds: 147}2 分 27 秒 147 秒响应方向翻译定时器启动提示与到时提示这两条英语文本分别被翻译回法语from en-US to fr-FR并播放。整条链路验证了两点翻译函数的双向调用都正常工作且语言理解LUIS始终在服务端语言上运行无需为每种用户语言重新训练模型——这正是「两端加翻译、核心不动」架构的实际效果。源码参考索引本文涉及的全部实现均可在此仓库中直接查看云端翻译函数6-consumer/lessons/4-multiple-language-support/code/functions/smart-timer-trigger/translate-text/__init__.py触发器绑定配置6-consumer/lessons/4-multiple-language-support/code/functions/smart-timer-trigger/translate-text/function.json函数应用本地配置含 TRANSLATOR_KEY / TRANSLATOR_LOCATION6-consumer/lessons/4-multiple-language-support/code/functions/smart-timer-trigger/local.settings.jsonWio Terminal 配置头文件LANGUAGE / SERVER_LANGUAGE / TRANSLATE_FUNCTION_URL 等6-consumer/lessons/4-multiple-language-support/code/wio-terminal/smart-timer/src/config.h翻译类实现6-consumer/lessons/4-multiple-language-support/code/wio-terminal/smart-timer/src/text_translator.h主流程集成say / processAudio6-consumer/lessons/4-multiple-language-support/code/wio-terminal/smart-timer/src/main.cppPlatformIO 工程配置6-consumer/lessons/4-multiple-language-support/code/wio-terminal/smart-timer/platformio.ini本课完整讲解含翻译原理、Translator 资源创建命令与课后挑战6-consumer/lessons/4-multiple-language-support/README.md本课是「消费级 IoT」项目的最后一课课程建议在完成课后作业构建「万能翻译器」之后再清理云资源如需清理指引可参考仓库根目录的 clean-up.md。【免费下载链接】IoT-For-Beginners12 Weeks, 24 Lessons, IoT for All!项目地址: https://gitcode.com/GitHub_Trending/io/IoT-For-Beginners创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考