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/ko/2-farm/lessons/4-migrate-your-plant-to-the.../wio-terminal-connect-hub.md

304 lines
13 KiB

<!--
CO_OP_TRANSLATOR_METADATA:
{
"original_hash": "28320305a35ea3bc59c41fe146a2e6ed",
"translation_date": "2025-08-24T22:51:12+00:00",
"source_file": "2-farm/lessons/4-migrate-your-plant-to-the-cloud/wio-terminal-connect-hub.md",
"language_code": "ko"
}
-->
# 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);
}
```
이 코드는 강의 범위를 벗어납니다. NTP 서버에서 현재 시간을 가져와 Wio Terminal의 시계를 설정하는 `initTime`이라는 함수를 정의합니다.
1. `main.cpp` 파일을 열고, `PubSubClient.h` 헤더 파일, `PubSubClient` 변수 선언, `reconnectMQTTClient``createMQTTClient` 메서드, 그리고 이 변수와 메서드에 대한 모든 호출을 포함한 모든 MQTT 코드를 제거합니다. 이 파일에는 WiFi에 연결하고, 토양 습도를 가져오고, 이를 JSON 문서로 생성하는 코드만 포함되어야 합니다.
1. IoT Hub 라이브러리와 시간을 설정하기 위한 헤더 파일을 포함하려면, `main.cpp` 파일 상단에 다음 `#include` 지시문을 추가하세요:
```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;
```
이는 IoT Hub에 대한 연결 핸들인 `IOTHUB_DEVICE_CLIENT_LL_HANDLE`을 선언합니다.
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 클라이언트와 마찬가지로, 이 코드는 단일 스레드에서 실행되므로 허브에서 보내고 허브로 보내는 메시지를 처리할 시간이 필요합니다. 이를 위해 `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 시간이 가져온 후 디바이스 클라이언트가 연결되는 것을 볼 수 있습니다. 연결에는 몇 초가 걸릴 수 있으므로, 디바이스가 연결되는 동안 출력에서 토양 습도를 볼 수 있습니다.
> 💁 NTP의 UNIX 시간을 [unixtimestamp.com](https://www.unixtimestamp.com)과 같은 웹사이트를 사용하여 더 읽기 쉬운 형식으로 변환할 수 있습니다.
## 텔레메트리 전송
이제 디바이스가 연결되었으므로, MQTT 브로커 대신 IoT Hub로 텔레메트리를 전송할 수 있습니다.
### 작업 - 텔레메트리 전송
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 메시지를 생성하고, 이를 허브로 전송한 뒤 메시지 객체를 정리합니다.
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` 함수는 IoT Hub에서 전송한 이벤트를 처리하기 위해 `IoTHubDeviceClient_LL_DoWork` 함수를 호출합니다. 이는 `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` 호출하고 100ms 동안 지연합니다. 이는 주어진 `delay_time` 매개변수만큼 지연하기 위해 필요한 만큼 반복됩니다. 이로 인해 디바이스는 직접 메서드 요청을 처리하기 위해 최대 100ms 대기합니다.
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) 사용하여 번역되었습니다. 정확성을 위해 최선을 다하고 있지만, 자동 번역에는 오류나 부정확성이 포함될 있습니다. 원본 문서를 해당 언어로 작성된 상태에서 권위 있는 자료로 간주해야 합니다. 중요한 정보의 경우, 전문적인 인간 번역을 권장합니다. 번역 사용으로 인해 발생하는 오해나 잘못된 해석에 대해 당사는 책임을 지지 않습니다.