將裝置連接至遠端監視預先設定方案 (Windows)
案例概觀
在此案例中,您會建立一個裝置,此裝置會將下列遙測資料傳送給遠端監視預先設定解決方案:
- 外部溫度
- 內部溫度
- 溼度
為了簡化起見,在裝置上的程式碼會產生範例值,但我們鼓勵您將實際的感應器連接到您的裝置並傳送實際的遙測資料來擴充此範例。
此裝置同時也能夠回應從解決方案儀表板叫用的方法,以及回應解決方案儀表板中設定的所需屬性值。
若要完成此教學課程,您需要一個有效的 Azure 帳戶。 如果您沒有帳戶,只需要幾分鐘的時間就可以建立免費試用帳戶。 如需詳細資料,請參閱 Azure 免費試用。
開始之前
在您為裝置撰寫任何程式碼之前,您必須先佈建遠端監視預先設定解決方案,並在該解決方案中佈建新的自訂裝置。
佈建遠端監視預先設定解決方案
您在本教學課程中建立的裝置會將資料傳送給遠端監視預先設定解決方案的執行個體。 如果您尚未在您的 Azure 帳戶中佈建遠端監視預先設定解決方案,請依照下列步驟執行:
- 在 https://www.azureiotsolutions.com/ 頁面上,按一下 + 以建立解決方案。
- 按一下 [遠端監視] 面板上的 [選取],以建立解決方案。
- 在 [建立遠端監視解決方案] 頁面上,輸入 您選擇的 [解決方案名稱],選取您要部署的 [區域],並選取想要使用的 Azure 訂用帳戶。 按一下 [建立解決方案] 。
- 等候佈建程序完成。
警告
預先設定的解決方案使用可計費的 Azure 服務。 當您使用完預先設定的解決方案之後,請務必將它從您的訂用帳戶中移除,以避免任何不必要的費用。 您可以造訪 https://www.azureiotsolutions.com/ 頁面,以從訂用帳戶中完全移除預先設定的解決方案。
當遠端監視解決方案的佈建程序完成之後,請按一下 [啟動] ,以在瀏覽器中開啟解決方案儀表板。
在遠端監視解決方案中佈建您的裝置
注意
如果您已經在解決方案中佈建裝置,則可以略過此步驟。 建立用戶端應用程式時,您需要知道裝置認證。
對於連線到預先設定解決方案的裝置,該裝置必須使用有效的認證向 IoT 中樞識別自己。 您會從解決方案儀表板收到裝置認證。 稍後在本教學課程中,您會將您的裝置認證包含在您的用戶端應用程式中。
若要在您的遠端監視解決方案中新增裝置,請在解決方案儀表板中完成下列步驟:
在儀表板左下角,按一下 [新增裝置] 。
在 [自訂裝置] 面板中,按一下 [新增]。
選擇 [讓我定義自己的裝置識別碼]。 輸入裝置識別碼 (例如 mydevice),按一下 [檢查 ID] 以確認沒有其他裝置使用該名稱,然後按一下 [建立] 來佈建裝置。
記下裝置認證 (裝置識別碼、IoT 中樞主機名稱及裝置金鑰)。 用戶端應用程式需要這些值,才能連接到遠端監視解決方案。 然後按一下 [完成]。
從解決方案儀表板的裝置清單中選取您的裝置。 然後,在 [裝置詳細資料] 面板中,按一下 [啟用裝置]。 裝置的狀態現在會是 [正在執行]。 遠端監視解決方案現在已可從您的裝置接收遙測資料,並在該裝置上叫用方法。
在 Windows 上建立 C 範例方案
下列步驟說明如何建立用戶端應用程式來與預先設定的遠端監視解決方案進行通訊。 此應用程式是以 C 撰寫並在 Windows 上建置和執行。
在 Visual Studio 2015 或 Visual Studio 2017 中建立入門專案,並新增 IoT 中樞的裝置用戶端 NuGet 套件︰
在 Visual Studio 中,使用 Visual C++ Win32 主控台應用程式 範本建立 C 主控台應用程式。 將專案命名為 RMDevice。
在 [Win32 應用程式精靈] 的 [應用程式設定] 頁面中,確定已選取 [主控台應用程式],並取消核取 [預先編譯的標頭] 和 [安全性開發生命週期 (SDL) 檢查]。
在 [方案總管] 中刪除檔案 stdafx.h、targetver.h 和 stdafx.cpp。
在 [方案總管] 中將檔案 RMDevice.cpp 重新命名為 RMDevice.c。
在 [方案總管] 中,以滑鼠右鍵按一下 [RMDevice] 專案,然後按一下 [管理 NuGet 套件]。 按一下 [瀏覽],然後搜尋並安裝下列 NuGet 套件︰
- Microsoft.Azure.IoTHub.Serializer
- Microsoft.Azure.IoTHub.IoTHubClient
- Microsoft.Azure.IoTHub.MqttTransport
在 [方案總管] 中,以滑鼠右鍵按一下 [RMDevice] 專案,然後按一下 [屬性] 開啟專案的 [屬性頁] 對話方塊。 如需詳細資訊,請參閱設定 Visual C++ 專案屬性。
按一下 [連結器]資料夾,然後按一下 [輸入] 屬性頁。
將 crypt32.lib 新增至 [其他相依性] 屬性。 按一下 [確定],然後再按一下 [確定] 以儲存專案屬性值。
將 Parson JSON 程式庫新增至 RMDevice 專案,並新增所需的 #include
陳述式︰
在您電腦上的適當資料夾中,使用下列命令複製 Parson GitHub 儲存機制︰
git clone https://github.com/kgabis/parson.git
將 Parson.h 和 parson.c 檔案從 Parson 儲存機制的本機複本複製到 RMDevice 專案資料夾。
在 Visual Studio 中,以滑鼠右鍵按一下 RMDevice 專案,按一下 [新增],然後按一下 [現有項目]。
在 [新增現有項目] 對話方塊中,選取 RMDevice 專案資料夾中的 parson.h 和 parson.c 檔案。 然後按一下 [新增],將這兩個檔案新增至您的專案。
在 Visual Studio 中,開啟 RMDevice.c 檔案。 以下列程式碼取代現有
#include
陳述式:#include "iothubtransportmqtt.h" #include "schemalib.h" #include "iothub_client.h" #include "serializer_devicetwin.h" #include "schemaserializer.h" #include "azure_c_shared_utility/threadapi.h" #include "azure_c_shared_utility/platform.h" #include "parson.h"
注意
現在,您可以藉由建置專案來確認專案已設定正確的相依性。
指定 IoT 裝置的行為
IoT 中樞序列化程式用戶端程式庫會使用模型來指定裝置與 IoT 中樞交換之訊息的格式。
在
#include
陳述式之後新增下列變數宣告。 使用您在遠端監視解決方案儀表板中為裝置記下的值來取代 [Device ID] 和 [Device Key] 預留位置值。 使用解決方案儀表板中的「IoT 中樞主機名稱」來取代 [IoTHub Name]。 例如,若您的 IoT 中樞主機名稱是 contoso.azure-devices.net,請使用 contoso 取代 [IoTHub Name]:static const char* deviceId = "[Device Id]"; static const char* connectionString = "HostName=[IoTHub Name].azure-devices.net;DeviceId=[Device Id];SharedAccessKey=[Device Key]";
新增下列程式碼以定義可讓裝置與 IoT 中樞通訊的模型。 此模型會指定裝置:
- 可以傳送溫度、外部溫度、濕度及裝置識別碼作為遙測資料。
- 可以將裝置相關中繼資料傳送給 IoT 中樞。 裝置會在啟動時傳送 DeviceInfo 物件中的基本中繼資料。
- 可以將回報的屬性傳送給 IoT 中樞內的裝置對應項。 這些回報的屬性會依組態、裝置及系統屬性分組。
- 可以接收 IoT 中樞內裝置對應項中設定的所需屬性,並根據這些屬性採取動作。
- 可以回應透過解決方案入口網站叫用的 Reboot 和 InitiateFirmwareUpdate 直接方法。 裝置會使用回報的屬性來傳送它所支援之直接方法的相關資訊。
// Define the Model BEGIN_NAMESPACE(Contoso); /* Reported properties */ DECLARE_STRUCT(SystemProperties, ascii_char_ptr, Manufacturer, ascii_char_ptr, FirmwareVersion, ascii_char_ptr, InstalledRAM, ascii_char_ptr, ModelNumber, ascii_char_ptr, Platform, ascii_char_ptr, Processor, ascii_char_ptr, SerialNumber ); DECLARE_STRUCT(LocationProperties, double, Latitude, double, Longitude ); DECLARE_STRUCT(ReportedDeviceProperties, ascii_char_ptr, DeviceState, LocationProperties, Location ); DECLARE_MODEL(ConfigProperties, WITH_REPORTED_PROPERTY(double, TemperatureMeanValue), WITH_REPORTED_PROPERTY(uint8_t, TelemetryInterval) ); /* Part of DeviceInfo */ DECLARE_STRUCT(DeviceProperties, ascii_char_ptr, DeviceID, _Bool, HubEnabledState ); DECLARE_DEVICETWIN_MODEL(Thermostat, /* Telemetry (temperature, external temperature and humidity) */ WITH_DATA(double, Temperature), WITH_DATA(double, ExternalTemperature), WITH_DATA(double, Humidity), WITH_DATA(ascii_char_ptr, DeviceId), /* DeviceInfo */ WITH_DATA(ascii_char_ptr, ObjectType), WITH_DATA(_Bool, IsSimulatedDevice), WITH_DATA(ascii_char_ptr, Version), WITH_DATA(DeviceProperties, DeviceProperties), /* Device twin properties */ WITH_REPORTED_PROPERTY(ReportedDeviceProperties, Device), WITH_REPORTED_PROPERTY(ConfigProperties, Config), WITH_REPORTED_PROPERTY(SystemProperties, System), WITH_DESIRED_PROPERTY(double, TemperatureMeanValue, onDesiredTemperatureMeanValue), WITH_DESIRED_PROPERTY(uint8_t, TelemetryInterval, onDesiredTelemetryInterval), /* Direct methods implemented by the device */ WITH_METHOD(Reboot), WITH_METHOD(InitiateFirmwareUpdate, ascii_char_ptr, FwPackageURI), /* Register direct methods with solution portal */ WITH_REPORTED_PROPERTY(ascii_char_ptr_no_quotes, SupportedMethods) ); END_NAMESPACE(Contoso);
實作裝置的行為
現在,請新增程式碼來實作模型中所定義的行為。
新增下列函式,這些函式會處理在解決方案儀表板中設定的所需屬性。 這些所需屬性是在模型中定義:
void onDesiredTemperatureMeanValue(void* argument) { /* By convention 'argument' is of the type of the MODEL */ Thermostat* thermostat = argument; printf("Received a new desired_TemperatureMeanValue = %f\r\n", thermostat->TemperatureMeanValue); } void onDesiredTelemetryInterval(void* argument) { /* By convention 'argument' is of the type of the MODEL */ Thermostat* thermostat = argument; printf("Received a new desired_TelemetryInterval = %d\r\n", thermostat->TelemetryInterval); }
新增下列函式,這些函式會處理透過 IoT 中樞叫用的直接方法。 這些直接方法是在模型中定義:
/* Handlers for direct methods */ METHODRETURN_HANDLE Reboot(Thermostat* thermostat) { (void)(thermostat); METHODRETURN_HANDLE result = MethodReturn_Create(201, "\"Rebooting\""); printf("Received reboot request\r\n"); return result; } METHODRETURN_HANDLE InitiateFirmwareUpdate(Thermostat* thermostat, ascii_char_ptr FwPackageURI) { (void)(thermostat); METHODRETURN_HANDLE result = MethodReturn_Create(201, "\"Initiating Firmware Update\""); printf("Recieved firmware update request. Use package at: %s\r\n", FwPackageURI); return result; }
新增下列函式,此函式會傳送訊息給預先設定的解決方案:
/* Send data to IoT Hub */ static void sendMessage(IOTHUB_CLIENT_HANDLE iotHubClientHandle, const unsigned char* buffer, size_t size) { IOTHUB_MESSAGE_HANDLE messageHandle = IoTHubMessage_CreateFromByteArray(buffer, size); if (messageHandle == NULL) { printf("unable to create a new IoTHubMessage\r\n"); } else { if (IoTHubClient_SendEventAsync(iotHubClientHandle, messageHandle, NULL, NULL) != IOTHUB_CLIENT_OK) { printf("failed to hand over the message to IoTHubClient"); } else { printf("IoTHubClient accepted the message for delivery\r\n"); } IoTHubMessage_Destroy(messageHandle); } free((void*)buffer); }
新增下列回呼處理常式,此處理常式會在裝置將新回報的屬性值傳送給預先設定的解決方案後執行:
/* Callback after sending reported properties */ void deviceTwinCallback(int status_code, void* userContextCallback) { (void)(userContextCallback); printf("IoTHub: reported properties delivered with status_code = %u\n", status_code); }
新增下列函式,以將您的裝置連接到雲端中的預先設定解決方案,然後交換資料。 此函式會執行下列步驟:
- 將平台初始化。
- 使用序列化程式庫來註冊 Contoso 命名空間。
- 使用裝置連接字串將用戶端初始化。
- 建立「控溫器」模型的執行個體。
- 建立並傳送回報的屬性值。
- 傳送 DeviceInfo 物件。
- 建立迴圈來每秒傳送遙測資料。
- 將所有資源取消初始化。
void remote_monitoring_run(void) { if (platform_init() != 0) { printf("Failed to initialize the platform.\n"); } else { if (SERIALIZER_REGISTER_NAMESPACE(Contoso) == NULL) { printf("Unable to SERIALIZER_REGISTER_NAMESPACE\n"); } else { IOTHUB_CLIENT_HANDLE iotHubClientHandle = IoTHubClient_CreateFromConnectionString(connectionString, MQTT_Protocol); if (iotHubClientHandle == NULL) { printf("Failure in IoTHubClient_CreateFromConnectionString\n"); } else { #ifdef MBED_BUILD_TIMESTAMP // For mbed add the certificate information if (IoTHubClient_SetOption(iotHubClientHandle, "TrustedCerts", certificates) != IOTHUB_CLIENT_OK) { printf("Failed to set option \"TrustedCerts\"\n"); } #endif // MBED_BUILD_TIMESTAMP Thermostat* thermostat = IoTHubDeviceTwin_CreateThermostat(iotHubClientHandle); if (thermostat == NULL) { printf("Failure in IoTHubDeviceTwin_CreateThermostat\n"); } else { /* Set values for reported properties */ thermostat->Config.TemperatureMeanValue = 55.5; thermostat->Config.TelemetryInterval = 3; thermostat->Device.DeviceState = "normal"; thermostat->Device.Location.Latitude = 47.642877; thermostat->Device.Location.Longitude = -122.125497; thermostat->System.Manufacturer = "Contoso Inc."; thermostat->System.FirmwareVersion = "2.22"; thermostat->System.InstalledRAM = "8 MB"; thermostat->System.ModelNumber = "DB-14"; thermostat->System.Platform = "Plat 9.75"; thermostat->System.Processor = "i3-7"; thermostat->System.SerialNumber = "SER21"; /* Specify the signatures of the supported direct methods */ thermostat->SupportedMethods = "{\"Reboot\": \"Reboot the device\", \"InitiateFirmwareUpdate--FwPackageURI-string\": \"Updates device Firmware. Use parameter FwPackageURI to specify the URI of the firmware file\"}"; /* Send reported properties to IoT Hub */ if (IoTHubDeviceTwin_SendReportedStateThermostat(thermostat, deviceTwinCallback, NULL) != IOTHUB_CLIENT_OK) { printf("Failed sending serialized reported state\n"); } else { printf("Send DeviceInfo object to IoT Hub at startup\n"); thermostat->ObjectType = "DeviceInfo"; thermostat->IsSimulatedDevice = 0; thermostat->Version = "1.0"; thermostat->DeviceProperties.HubEnabledState = 1; thermostat->DeviceProperties.DeviceID = (char*)deviceId; unsigned char* buffer; size_t bufferSize; if (SERIALIZE(&buffer, &bufferSize, thermostat->ObjectType, thermostat->Version, thermostat->IsSimulatedDevice, thermostat->DeviceProperties) != CODEFIRST_OK) { (void)printf("Failed serializing DeviceInfo\n"); } else { sendMessage(iotHubClientHandle, buffer, bufferSize); } /* Send telemetry */ thermostat->Temperature = 50; thermostat->ExternalTemperature = 55; thermostat->Humidity = 50; thermostat->DeviceId = (char*)deviceId; while (1) { unsigned char*buffer; size_t bufferSize; (void)printf("Sending sensor value Temperature = %f, Humidity = %f\n", thermostat->Temperature, thermostat->Humidity); if (SERIALIZE(&buffer, &bufferSize, thermostat->DeviceId, thermostat->Temperature, thermostat->Humidity, thermostat->ExternalTemperature) != CODEFIRST_OK) { (void)printf("Failed sending sensor value\r\n"); } else { sendMessage(iotHubClientHandle, buffer, bufferSize); } ThreadAPI_Sleep(1000); } IoTHubDeviceTwin_DestroyThermostat(thermostat); } } IoTHubClient_Destroy(iotHubClientHandle); } serializer_deinit(); } } platform_deinit(); }
以下是一個傳送給預先設定之解決方案的範例「遙測」訊息,可供您參考:
{"DeviceId":"mydevice01", "Temperature":50, "Humidity":50, "ExternalTemperature":55}
建置並執行範例
新增程式碼以叫用 remote_monitoring_run 函式,然後建置並執行裝置應用程式。
將 main 函式取代為下列程式碼以叫用 remote_monitoring_run 函式︰
int main() { remote_monitoring_run(); return 0; }
依序按一下 [建置] 和 [建置方案] 以建置裝置應用程式。
在 [方案總管] 中,以滑鼠右鍵按一下 [RMDevice] 專案,並按一下 [偵錯],然後按一下 [開始新執行個體] 來執行範例。 主控台會在應用程式將範例遙測傳送至預先設定的解決方案時顯示訊息,接收在解決方案儀表板中設定的所需屬性值,以及回應從解決方案儀表板叫用的方法。
在儀表板中檢視裝置遙測
遠端監視解決方案中的儀表板可讓您檢視裝置傳送到 IoT 中樞的遙測資料。
在瀏覽器中,返回遠端監視解決方案儀表板,按一下左面板中的 [裝置] 瀏覽至 [裝置清單]。
在 [裝置清單] 中,您應該會看到裝置的狀態是 [正在執行]。 如果不是,請按一下 [裝置詳細資料] 面板中的 [啟用裝置]。
按一下 [儀表板] 以返回儀表板,從 [要檢視的裝置] 下拉式清單中選取您的裝置,以檢視其遙測。 範例應用程式的遙測是 50 個單位的內部溫度、 55 個單位的外部溫度,以及 50 個單位的濕度。
在裝置上叫用方法
遠端監視解決方案中的儀表板可讓您透過 IoT 中樞在裝置上叫用方法。 例如,在遠端監視解決方案中,您可以叫用方法來模擬裝置重新啟動。
在遠端監視解決方案的儀表板中,按一下左面板中的 [裝置] 瀏覽至 [裝置清單]。
在 [裝置清單] 中,按一下裝置的 [裝置識別碼]。
在 [裝置詳細資料] 面板中,按一下 [方法]。
在 [方法] 下拉式清單中,選取 [InitiateFirmwareUpdate],然後在 [FWPACKAGEURI] 中輸入虛擬的 URL。 按一下 [叫用方法] 以在裝置上呼叫該方法。
當裝置處理該方法時,您可以在執行裝置程式碼的主控台中看見訊息。 該方法的結果會新增到解決方案入口網站的歷程記錄中:
後續步驟
自訂預先設定的方案一文描述可供您擴充此範例的一些方式。 可能的延伸模組包括使用真實的感應器和實作其他命令。
您可以深入了解 azureiotsuite.com 網站的權限。