見出し画像

35. 最新の IoT Edge Module 開発環境を試す

前回の記事                        次回の記事

はじめに

前回の記事を書く時、久しぶりに、Visual Studio Code + IoT Edge 拡張で新しく IoT Edge Module を作ってみたら、生成された C# コードがかなり変わっていて驚きました。今回は、最新の IoT Edge 拡張で生成したコードをガン見することにします。

おさらい

Visual Studio Code に IoT Edge 拡張をインストールすると、IoT Edge Module を開発できるようになるのは、以前からこの定期購読マガジンでも紹介してきました。この開発環境について、簡単に振り返りを行います。
開発に関する概要は、

Visual Studio Code を使用して Azure IoT Edge モジュールを開発およびデバッグする | Microsoft Learn

に書いてあるので、こちらを参照してもらうとして、ポイントだけ書いておきます。…まてまて、このリンク、Visual Studio Code + IoT Edge 拡張による開発に関するものではないですね。PowerShell や Bash 等のコマンドラインでの開発方法の説明が書いてありました。Visual Studio Code は開発したコードのリモートデバッグで使うという事です。まぁ、この記事も実際の開発では役立つ情報なので、覚えておくとよいでしょう。

で、Visual Studio Code + IoT Edge 拡張のセッティングや、IoT Edge Module の開発については、

Visual Studio Code を使用して Azure IoT Edge モジュールを開発およびデバッグする | Microsoft Learn

こちらの方でした。あれ?字面一緒?それぞれの URL を確認すると、最後の pivots の値が異なります。

Windows PC で Visual Studio Code + IoT Edge 拡張 を使うには、

が、必要です。ってことは、Windows Home でなく、Windows Professional 以上の OS が必要という事ですね。ご注意ください。
Visual Studio Code は、https://visualstudio.microsoft.com/  からインストーラをダウンロード・実行すれば簡単にインストールできます。
IoT Edge 拡張は、Azure IoT Edge - Visual Studio Marketplace から公開されています。インストールは簡単で、

IoT Edge 拡張のインストール

Visual Studio Code の Extension で ”Azure IoT Edge” を検索してインストールを行います。
この拡張ツールでは、C/C++、C#、Node.JS、Python 等、様々なプログラミング言語での開発が可能ですが、それぞれ、

の拡張インストールが必要です。他にも、C# の場合は、.NET Framework SDK、Node.JS の場合は、node.js と npm の、Windows PC へのインストールが必要です。

最新の Visual Studio Code +IoT Edge extension で IoT Edge Module プロジェクトを作ってみる

それでは、2023/2/21 現在の最新の Azure IoT Edge 拡張で、C言語、C#、Node.js、Python の IoT Edge Module プロジェクトを作ってみます。
Azure IoT Edge のバージョンは、1.25.1 です。

C言語

生成されたファイル群は以下の通りです。構成自体は変わっていません。

生成されたファイルセット

main.c のコードは、

// Copyright (c) Microsoft. All rights reserved.
// Licensed under the MIT license. See LICENSE file in the project root for full license information.

#include <stdio.h>
#include <stdlib.h>

#include "iothub_module_client_ll.h"
#include "iothub_client_options.h"
#include "iothub_message.h"
#include "azure_c_shared_utility/threadapi.h"
#include "azure_c_shared_utility/crt_abstractions.h"
#include "azure_c_shared_utility/platform.h"
#include "azure_c_shared_utility/shared_util_options.h"
#include "iothubtransportmqtt.h"
#include "iothub.h"
#include "time.h"

typedef struct MESSAGE_INSTANCE_TAG
{
    IOTHUB_MESSAGE_HANDLE messageHandle;
    size_t messageTrackingId;  // For tracking the messages within the user callback.
} 
MESSAGE_INSTANCE;

size_t messagesReceivedByInput1Queue = 0;

// SendConfirmationCallback is invoked when the message that was forwarded on from 'InputQueue1Callback'
// pipeline function is confirmed.
static void SendConfirmationCallback(IOTHUB_CLIENT_CONFIRMATION_RESULT result, void* userContextCallback)
{
    // The context corresponds to which message# we were at when we sent.
    MESSAGE_INSTANCE* messageInstance = (MESSAGE_INSTANCE*)userContextCallback;
    printf("Confirmation[%zu] received for message with result = %d\r\n", messageInstance->messageTrackingId, result);
    IoTHubMessage_Destroy(messageInstance->messageHandle);
    free(messageInstance);
}

// Allocates a context for callback and clones the message
// NOTE: The message MUST be cloned at this stage.  InputQueue1Callback's caller always frees the message
// so we need to pass down a new copy.
static MESSAGE_INSTANCE* CreateMessageInstance(IOTHUB_MESSAGE_HANDLE message)
{
    MESSAGE_INSTANCE* messageInstance = (MESSAGE_INSTANCE*)malloc(sizeof(MESSAGE_INSTANCE));
    if (NULL == messageInstance)
    {
        printf("Failed allocating 'MESSAGE_INSTANCE' for pipelined message\r\n");
    }
    else
    {
        memset(messageInstance, 0, sizeof(*messageInstance));

        if ((messageInstance->messageHandle = IoTHubMessage_Clone(message)) == NULL)
        {
            free(messageInstance);
            messageInstance = NULL;
        }
        else
        {
            messageInstance->messageTrackingId = messagesReceivedByInput1Queue;
        }
    }

    return messageInstance;
}

static IOTHUBMESSAGE_DISPOSITION_RESULT InputQueue1Callback(IOTHUB_MESSAGE_HANDLE message, void* userContextCallback)
{
    IOTHUBMESSAGE_DISPOSITION_RESULT result;
    IOTHUB_CLIENT_RESULT clientResult;
    IOTHUB_MODULE_CLIENT_LL_HANDLE iotHubModuleClientHandle = (IOTHUB_MODULE_CLIENT_LL_HANDLE)userContextCallback;

    unsigned const char* messageBody;
    size_t contentSize;

    if (IoTHubMessage_GetByteArray(message, &messageBody, &contentSize) != IOTHUB_MESSAGE_OK)
    {
        messageBody = "<null>";
    }

    printf("Received Message [%zu]\r\n Data: [%s]\r\n", 
            messagesReceivedByInput1Queue, messageBody);

    // This message should be sent to next stop in the pipeline, namely "output1".  What happens at "outpu1" is determined
    // by the configuration of the Edge routing table setup.
    MESSAGE_INSTANCE *messageInstance = CreateMessageInstance(message);
    if (NULL == messageInstance)
    {
        result = IOTHUBMESSAGE_ABANDONED;
    }
    else
    {
        printf("Sending message (%zu) to the next stage in pipeline\n", messagesReceivedByInput1Queue);

        clientResult = IoTHubModuleClient_LL_SendEventToOutputAsync(iotHubModuleClientHandle, messageInstance->messageHandle, "output1", SendConfirmationCallback, (void *)messageInstance);
        if (clientResult != IOTHUB_CLIENT_OK)
        {
            IoTHubMessage_Destroy(messageInstance->messageHandle);
            free(messageInstance);
            printf("IoTHubModuleClient_LL_SendEventToOutputAsync failed on sending msg#=%zu, err=%d\n", messagesReceivedByInput1Queue, clientResult);
            result = IOTHUBMESSAGE_ABANDONED;
        }
        else
        {
            result = IOTHUBMESSAGE_ACCEPTED;
        }
    }

    messagesReceivedByInput1Queue++;
    return result;
}

static IOTHUB_MODULE_CLIENT_LL_HANDLE InitializeConnection()
{
    IOTHUB_MODULE_CLIENT_LL_HANDLE iotHubModuleClientHandle;

    if (IoTHub_Init() != 0)
    {
        printf("Failed to initialize the platform.\r\n");
        iotHubModuleClientHandle = NULL;
    }
    else if ((iotHubModuleClientHandle = IoTHubModuleClient_LL_CreateFromEnvironment(MQTT_Protocol)) == NULL)
    {
        printf("ERROR: IoTHubModuleClient_LL_CreateFromEnvironment failed\r\n");
    }
    else
    {
        // Uncomment the following lines to enable verbose logging.
        // bool traceOn = true;
        // IoTHubModuleClient_LL_SetOption(iotHubModuleClientHandle, OPTION_LOG_TRACE, &trace);
    }

    return iotHubModuleClientHandle;
}

static void DeInitializeConnection(IOTHUB_MODULE_CLIENT_LL_HANDLE iotHubModuleClientHandle)
{
    if (iotHubModuleClientHandle != NULL)
    {
        IoTHubModuleClient_LL_Destroy(iotHubModuleClientHandle);
    }
    IoTHub_Deinit();
}

static int SetupCallbacksForModule(IOTHUB_MODULE_CLIENT_LL_HANDLE iotHubModuleClientHandle)
{
    int ret;

    if (IoTHubModuleClient_LL_SetInputMessageCallback(iotHubModuleClientHandle, "input1", InputQueue1Callback, (void*)iotHubModuleClientHandle) != IOTHUB_CLIENT_OK)
    {
        printf("ERROR: IoTHubModuleClient_LL_SetInputMessageCallback(\"input1\")..........FAILED!\r\n");
        ret = 1;
    }
    else
    {
        ret = 0;
    }

    return ret;
}

void iothub_module()
{
    IOTHUB_MODULE_CLIENT_LL_HANDLE iotHubModuleClientHandle;

    srand((unsigned int)time(NULL));

    if ((iotHubModuleClientHandle = InitializeConnection()) != NULL && SetupCallbacksForModule(iotHubModuleClientHandle) == 0)
    {
        // The receiver just loops constantly waiting for messages.
        printf("Waiting for incoming messages.\r\n");
        while (true)
        {
            IoTHubModuleClient_LL_DoWork(iotHubModuleClientHandle);
            ThreadAPI_Sleep(100);
        }
    }

    DeInitializeConnection(iotHubModuleClientHandle);
}

int main(void)
{
    iothub_module();
    return 0;
}

このコードは、以降で紹介する、C#、Node.JS、Python と同等のロジックです。ロゼッタストーン的に見てみるのも面白いでしょう。
次に、CMakeList.txt を見てみます。これは、C/C++ の開発プロジェクトでよく使われる Makefile 自動生成ツールの "cmake”用のファイルです。

cmake_minimum_required(VERSION 2.8.11)

macro(compileAsC99)
  if (CMAKE_VERSION VERSION_LESS "3.1")
    if (CMAKE_C_COMPILER_ID STREQUAL "GNU")
      set (CMAKE_C_FLAGS "--std=c99 ${CMAKE_C_FLAGS}")
      set (CMAKE_CXX_FLAGS "--std=c++11 ${CMAKE_CXX_FLAGS}")
    endif()
  else()
    set (CMAKE_C_STANDARD 99)
    set (CMAKE_CXX_STANDARD 11)
  endif()
endmacro(compileAsC99)

compileAsC99()

set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -DUSE_EDGE_MODULES")
set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -DUSE_EDGE_MODULES")

set(AZUREIOT_INC_FOLDER "/usr/include/azureiot" "/usr/include/azureiot/inc")

include_directories(${AZUREIOT_INC_FOLDER})

set(main_c_files
    ./main.c
)

add_executable(main ${main_c_files})

target_link_libraries(main
    iothub_client_mqtt_transport
    iothub_client
    parson
    umqtt
    prov_auth_client
    hsm_security_client
    uhttp
    aziotsharedutil
    pthread
    curl
    ssl
    crypto
    m
    uuid
)

次は、Dockerfile を見てみます。amd64 向けの、Dockerfile.amd64 を取り上げると、

FROM ubuntu:bionic AS base
RUN apt-get update && \
    apt-get install -y --no-install-recommends software-properties-common && \
    add-apt-repository -y ppa:aziotsdklinux/ppa-azureiot && \
    apt-get update && \
    apt-get install -y azure-iot-sdk-c-dev && \
    rm -rf /var/lib/apt/lists/*

FROM base AS build-env
RUN apt-get update && \
    apt-get install -y --no-install-recommends cmake gcc g++ make && \
    rm -rf /var/lib/apt/lists/* 
WORKDIR /app
COPY . ./
RUN cmake . 
RUN make

FROM base
WORKDIR /app
COPY --from=build-env /app ./
RUN useradd -ms /bin/bash moduleuser
USER moduleuser
CMD ["./main"]

Base Image は、ubuntu:bionic なので、Ubuntu 18.04 が使われています。C/C++ 言語の開発環境のインストールと、cmake により Makefile を生成して、make でビルドし、main という実行ファイルが作成されて、その実行ファイルを起動する、といった内容が定義されています。

まぁ、Docker テクノロジーを活用した IoT Edge Module では、C 言語を使っての開発はレアだと思うので、これくらいにしておきます。C言語については、後日また改めて、小規模 HW での IoT Hub 接続と絡めて深~く取り上げる予定です。

C#

C# は結構変わりました。

ここから先は

16,789字 / 7画像

2022年3月にマイクロソフトの中の人から外の人になった Embedded D. George が、現時点で持っている知識に加えて、頻繁に…

この記事が気に入ったらサポートをしてみませんか?