You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
IoT-For-Beginners/translations/zh/2-farm/lessons/4-migrate-your-plant-to-the.../wio-terminal-connect-hub.md

304 lines
11 KiB

This file contains ambiguous Unicode characters!

This file contains ambiguous Unicode characters that may be confused with others in your current locale. If your use case is intentional and legitimate, you can safely ignore this warning. Use the Escape button to highlight these characters.

<!--
CO_OP_TRANSLATOR_METADATA:
{
"original_hash": "28320305a35ea3bc59c41fe146a2e6ed",
"translation_date": "2025-08-24T22:49:39+00:00",
"source_file": "2-farm/lessons/4-migrate-your-plant-to-the-cloud/wio-terminal-connect-hub.md",
"language_code": "zh"
}
-->
# 将您的 IoT 设备连接到云端 - Wio Terminal
在本课程的这一部分,您将把 Wio Terminal 连接到 IoT Hub以发送遥测数据并接收命令。
## 将设备连接到 IoT Hub
下一步是将您的设备连接到 IoT Hub。
### 任务 - 连接到 IoT Hub
1. 在 VS Code 中打开 `soil-moisture-sensor` 项目。
1. 打开 `platformio.ini` 文件。移除 `knolleary/PubSubClient` 库依赖项。该库用于连接到公共 MQTT 代理,但连接到 IoT Hub时不需要。
1. 添加以下库依赖项:
```ini
seeed-studio/Seeed Arduino RTC @ 2.0.0
arduino-libraries/AzureIoTHub @ 1.6.0
azure/AzureIoTUtility @ 1.6.1
azure/AzureIoTProtocol_MQTT @ 1.6.0
azure/AzureIoTProtocol_HTTP @ 1.6.0
azure/AzureIoTSocket_WiFi @ 1.0.2
```
`Seeed Arduino RTC` 库提供与 Wio Terminal 的实时时钟交互的代码,用于跟踪时间。其余库允许您的 IoT 设备连接到 IoT Hub。
1.`platformio.ini` 文件底部添加以下内容:
```ini
build_flags =
-DDONT_USE_UPLOADTOBLOB
```
这设置了一个编译器标志,在编译 Arduino IoT Hub 代码时需要。
1. 打开 `config.h` 头文件。移除所有 MQTT 设置,并添加以下设备连接字符串常量:
```cpp
// IoT Hub settings
const char *CONNECTION_STRING = "<connection string>";
```
`<connection string>` 替换为您之前复制的设备连接字符串。
1. 连接到 IoT Hub 使用基于时间的令牌。这意味着 IoT 设备需要知道当前时间。与 Windows、macOS 或 Linux 等操作系统不同,微控制器不会自动通过互联网同步当前时间。这意味着您需要添加代码从 [NTP](https://wikipedia.org/wiki/Network_Time_Protocol) 服务器获取当前时间。一旦时间被检索,可以将其存储在 Wio Terminal 的实时时钟中,允许在设备未断电的情况下稍后请求正确时间。添加一个名为 `ntp.h` 的新文件,并包含以下代码:
```cpp
#pragma once
#include "DateTime.h"
#include <time.h>
#include "samd/NTPClientAz.h"
#include <sys/time.h>
static void initTime()
{
WiFiUDP _udp;
time_t epochTime = (time_t)-1;
NTPClientAz ntpClient;
ntpClient.begin();
while (true)
{
epochTime = ntpClient.getEpochTime("0.pool.ntp.org");
if (epochTime == (time_t)-1)
{
Serial.println("Fetching NTP epoch time failed! Waiting 2 seconds to retry.");
delay(2000);
}
else
{
Serial.print("Fetched NTP epoch time is: ");
char buff[32];
sprintf(buff, "%.f", difftime(epochTime, (time_t)0));
Serial.println(buff);
break;
}
}
ntpClient.end();
struct timeval tv;
tv.tv_sec = epochTime;
tv.tv_usec = 0;
settimeofday(&tv, NULL);
}
```
这些代码的细节超出了本课程的范围。它定义了一个名为 `initTime` 的函数,该函数从 NTP 服务器获取当前时间并用它设置 Wio Terminal 的时钟。
1. 打开 `main.cpp` 文件,移除所有 MQTT 代码,包括 `PubSubClient.h` 头文件、`PubSubClient` 变量的声明、`reconnectMQTTClient` 和 `createMQTTClient` 方法,以及对这些变量和方法的任何调用。该文件应仅包含连接到 WiFi、获取土壤湿度并创建 JSON 文档的代码。
1.`main.cpp` 文件顶部添加以下 `#include` 指令,以包含 IoT Hub 库和设置时间的头文件:
```cpp
#include <AzureIoTHub.h>
#include <AzureIoTProtocol_MQTT.h>
#include <iothubtransportmqtt.h>
#include "ntp.h"
```
1.`setup` 函数末尾添加以下调用以设置当前时间:
```cpp
initTime();
```
1. 在文件顶部的 `#include` 指令下方添加以下变量声明:
```cpp
IOTHUB_DEVICE_CLIENT_LL_HANDLE _device_ll_handle;
```
这声明了一个 `IOTHUB_DEVICE_CLIENT_LL_HANDLE`,用于连接到 IoT Hub 的句柄。
1. 在此下方添加以下代码:
```cpp
static void connectionStatusCallback(IOTHUB_CLIENT_CONNECTION_STATUS result, IOTHUB_CLIENT_CONNECTION_STATUS_REASON reason, void *user_context)
{
if (result == IOTHUB_CLIENT_CONNECTION_AUTHENTICATED)
{
Serial.println("The device client is connected to iothub");
}
else
{
Serial.println("The device client has been disconnected");
}
}
```
这声明了一个回调函数,当连接到 IoT Hub 的状态发生变化(例如连接或断开连接)时会被调用。状态会发送到串口。
1. 在此下方添加一个函数以连接到 IoT Hub
```cpp
void connectIoTHub()
{
IoTHub_Init();
_device_ll_handle = IoTHubDeviceClient_LL_CreateFromConnectionString(CONNECTION_STRING, MQTT_Protocol);
if (_device_ll_handle == NULL)
{
Serial.println("Failure creating Iothub device. Hint: Check your connection string.");
return;
}
IoTHubDeviceClient_LL_SetConnectionStatusCallback(_device_ll_handle, connectionStatusCallback, NULL);
}
```
此代码初始化 IoT Hub 库代码,然后使用 `config.h` 头文件中的连接字符串创建连接。此连接基于 MQTT。如果连接失败错误信息会发送到串口——如果您在输出中看到此信息请检查连接字符串。最后设置连接状态回调。
1.`setup` 函数中调用此函数,位置在 `initTime` 调用下方:
```cpp
connectIoTHub();
```
1. 与 MQTT 客户端类似,此代码运行在单线程上,因此需要时间处理由 Hub 发送和发送到 Hub 的消息。在 `loop` 函数顶部添加以下内容以实现此功能:
```cpp
IoTHubDeviceClient_LL_DoWork(_device_ll_handle);
```
1. 构建并上传此代码。您将在串口监视器中看到连接:
```output
Connecting to WiFi..
Connected!
Fetched NTP epoch time is: 1619983687
Sending telemetry {"soil_moisture":391}
The device client is connected to iothub
```
在输出中,您可以看到 NTP 时间被获取,然后设备客户端连接。连接可能需要几秒钟,因此在设备连接时,您可能会在输出中看到土壤湿度。
> 💁 您可以使用像 [unixtimestamp.com](https://www.unixtimestamp.com) 这样的网站将 NTP 的 UNIX 时间转换为更易读的格式。
## 发送遥测数据
现在您的设备已连接,您可以将遥测数据发送到 IoT Hub而不是 MQTT 代理。
### 任务 - 发送遥测数据
1.`setup` 函数上方添加以下函数:
```cpp
void sendTelemetry(const char *telemetry)
{
IOTHUB_MESSAGE_HANDLE message_handle = IoTHubMessage_CreateFromString(telemetry);
IoTHubDeviceClient_LL_SendEventAsync(_device_ll_handle, message_handle, NULL, NULL);
IoTHubMessage_Destroy(message_handle);
}
```
此代码从传递的字符串参数创建一个 IoT Hub 消息,将其发送到 Hub然后清理消息对象。
1.`loop` 函数中调用此代码,位置在将遥测数据发送到串口的行之后:
```cpp
sendTelemetry(telemetry.c_str());
```
## 处理命令
您的设备需要处理来自服务器代码的命令以控制继电器。这是通过直接方法请求发送的。
## 任务 - 处理直接方法请求
1.`connectIoTHub` 函数之前添加以下代码:
```cpp
int directMethodCallback(const char *method_name, const unsigned char *payload, size_t size, unsigned char **response, size_t *response_size, void *userContextCallback)
{
Serial.printf("Direct method received %s\r\n", method_name);
if (strcmp(method_name, "relay_on") == 0)
{
digitalWrite(PIN_WIRE_SCL, HIGH);
}
else if (strcmp(method_name, "relay_off") == 0)
{
digitalWrite(PIN_WIRE_SCL, LOW);
}
}
```
此代码定义了一个回调方法,当 IoT Hub 库收到直接方法请求时会调用该方法。请求的方法通过 `method_name` 参数传递。此函数将调用的方法打印到串口,然后根据方法名称打开或关闭继电器。
> 💁 这也可以通过单个直接方法请求实现,将继电器的期望状态作为有效负载传递,该有效负载可以通过方法请求传递并从 `payload` 参数中获取。
1.`directMethodCallback` 函数末尾添加以下代码:
```cpp
char resultBuff[16];
sprintf(resultBuff, "{\"Result\":\"\"}");
*response_size = strlen(resultBuff);
*response = (unsigned char *)malloc(*response_size);
memcpy(*response, resultBuff, *response_size);
return IOTHUB_CLIENT_OK;
```
直接方法请求需要响应,响应分为两部分——文本响应和返回码。此代码将创建以下 JSON 文档作为结果:
```JSON
{
"Result": ""
}
```
然后将其复制到 `response` 参数中,并在 `response_size` 参数中设置响应的大小。此代码随后返回 `IOTHUB_CLIENT_OK`,表示方法已正确处理。
1.`connectIoTHub` 函数末尾添加以下代码以连接回调:
```cpp
IoTHubClient_LL_SetDeviceMethodCallback(_device_ll_handle, directMethodCallback, NULL);
```
1. `loop` 函数将调用 `IoTHubDeviceClient_LL_DoWork` 函数以处理 IoT Hub 发送的事件。由于 `delay` 的原因,这仅每 10 秒调用一次,这意味着直接方法每 10 秒才会处理一次。为了提高效率,可以将 10 秒的延迟分成多个较短的延迟,每次调用 `IoTHubDeviceClient_LL_DoWork`。为此,在 `loop` 函数上方添加以下代码:
```cpp
void work_delay(int delay_time)
{
int current = 0;
do
{
IoTHubDeviceClient_LL_DoWork(_device_ll_handle);
delay(100);
current += 100;
} while (current < delay_time);
}
```
此代码将重复循环,调用 `IoTHubDeviceClient_LL_DoWork` 并每次延迟 100 毫秒。它会根据 `delay_time` 参数的值重复多次。这意味着设备最多等待 100 毫秒来处理直接方法请求。
1. `loop` 函数中,移除对 `IoTHubDeviceClient_LL_DoWork` 的调用,并将 `delay(10000)` 替换为以下代码以调用新函数:
```cpp
work_delay(10000);
```
> 💁 您可以在 [code/wio-terminal](../../../../../2-farm/lessons/4-migrate-your-plant-to-the-cloud/code/wio-terminal) 文件夹中找到此代码。
😀 您的土壤湿度传感器程序已连接到 IoT Hub
**免责声明**
本文档使用AI翻译服务[Co-op Translator](https://github.com/Azure/co-op-translator)进行翻译。尽管我们努力确保翻译的准确性,但请注意,自动翻译可能包含错误或不准确之处。原始语言的文档应被视为权威来源。对于关键信息,建议使用专业人工翻译。我们对因使用此翻译而产生的任何误解或误读不承担责任。