chakokuのブログ(rev4)

テック・コミック・DTM・・・ごくまれにチャリ

MicroPythonでAzure IoT Centralに接続してみる→Pythonですら難航中→なんとか動いた(正しくはpythonとAzure IoT Hub との間でPub/Sub)

Azure IoT Centralがどういうプロトコルで、どういう認証をやっているのか全く分からない。ちょっと調べると、IoT Hubを使ってデバイスと接続しているようだ。認証は、X.509とSASトークンがあるらしい。推奨はSASトークンのようだ。X.509が推奨されないのはなぜだろうか??
MicroPython用のSDKGitHubに出ている。最後はSDKを使うとしても、SDKに頼ってしまうとデバイスとIotHub間でどのような認証、通信をやってるのか全く分からないので、最初は素のMQTT Clientで接続したい。
GitHub - Azure/iot-central-micropython-client: A micropython SDK for connecting devices to Microsoft Azure IoT Central

SASトークンがまだ理解できておらずX.509で認証させたいので、自己署名でCAを作って、デバイス用証明書を作る必要がある。AWSのIoT CoreではAWS側がClient証明書を発行してくれるのだが、それは設計ポリシーの違いということか。
チュートリアル - OpenSSL を使用して Azure IoT Hub 向けの X.509 テスト証明書を作成する | Microsoft Learn
azure-iot-sdk-c/CACertificateOverview.md at main · Azure/azure-iot-sdk-c · GitHub

■追記
上記手順書に従って、ルート CA と下位 CA を作成した。これを一旦IoTHubに登録する必要がある。

■追記
下位CAを作成して、IoT Hubに登録した後、IoT Hubが提示するコード(数字の羅列)をSubjectとする証明書を発行して再度登録することで、所有証明が完了する*1。下位CAを使ってデバイスのClient証明書に署名して、このClient証明書を使ってMQTTでIoT Hubに接続する。
IoT Hubとの接続テストはMicrosoftからサンプルソースが提供されているのだがC#で提示されている。C#だとビルド環境の準備が大変で、さらにソースが結構複雑でよくわからない。というわけで、PythonのサンプルコードがないかChatに聞いてみた。回答は以下。このソースはシンプルで分かりやすい。まだ試していないのでエラーなく動くかどうかは不明。

import paho.mqtt.client as mqtt
import ssl
import os

# Azure IoT Hub connection settings
hub_address = "<your-hub-name>.azure-devices.net"
device_id = "<your-device-id>"
cert_file = "<path-to-cert-file>"
key_file = "<path-to-key-file>"
topic = "devices/" + device_id + "/messages/events/"

# SSL/TLS context
ssl_context = ssl.create_default_context()
ssl_context.check_hostname = False
ssl_context.verify_mode = ssl.CERT_NONE
ssl_context.load_cert_chain(certfile=cert_file, keyfile=key_file)

# MQTT client
client = mqtt.Client(client_id=device_id, protocol=mqtt.MQTTv311)
client.tls_set_context(context=ssl_context)

# Connect to Azure IoT Hub
client.tls_insecure_set(True)
client.username_pw_set(username=hub_address + "/" + device_id, password=None)
client.connect(host=hub_address, port=8883)

# Send a message
message = "Hello from Python"
client.publish(topic=topic, payload=message, qos=1)

# Disconnect from Azure IoT Hub
client.disconnect()

実行時Exception等はならなかったがPublishはされなかった。Connectで多分認証エラーになっていると思われる

Azure IoT Hub の MQTT サポートについて | Microsoft Learn
Azureの説明ページにMQTT CLientから呼び出す例があった。X.509の場合の例も示されている。どうもusernameの所が変わっている様だ。

■追記
動かなかった要素はいろいろありそうだが、動作確認できたコードは以下。動いたといっても、コンソール側でメッセージ送信のカウントが増えたことを根拠としており、Subscribeによる確認はまだ

#!/usr/bin/python3

# $ python3 -m pip install paho-mqtt

import paho.mqtt.client as mqtt
import ssl
import os
import time


HUB_NAME = "xxxxx"
DEVICE_ID = "xxxxxxx"

# Azure IoT Hub connection settings

ROOT_CA = "./root.ca"
CERT_FILE = "device.crt"
KEY_FILE = "device.key"


def on_connect(client,userdata,flag,rc):
   print("on connected:",str(rc))

def on_publish(client,userdata,mid):
   print("on published")

def on_disconnect(client,userdata,rc):
   print("on disconnected:",str(rc))


# MQTT client
client = mqtt.Client(client_id=DEVICE_ID, protocol=mqtt.MQTTv311)
client.on_connect = on_connect
client.on_disconnect = on_disconnect
client.on_publish = on_publish
   
hub_address = f"{HUB_NAME}.azure-devices.net"
user_name = f"{hub_address}/{DEVICE_ID}/?api-version=2021-04-12"
client.username_pw_set(username=user_name, password=None)

# SSL/TLS context
ssl_context = ssl.create_default_context()
ssl_context.check_hostname = False
ssl_context.verify_mode = ssl.CERT_NONE
client.tls_set(ca_certs=ROOT_CA, certfile=CERT_FILE,
               keyfile = KEY_FILE,
               cert_reqs=ssl.CERT_REQUIRED,
               tls_version = ssl.PROTOCOL_TLSv1_2, 
               ciphers=None)

client.tls_insecure_set(False)

# Connect to Azure IoT Hub

print(f"connect: {hub_address}")
client.connect(host=hub_address, port=8883)
time.sleep(1)

# start the MQTT processing loop
client.loop_start()

# Send a message
topic = f"devices/{DEVICE_ID}/messages/events/"
message = "Hello from Python?"
print(f"publish: {topic}")
client.publish(topic=topic, payload=message, qos=1)
time.sleep(3)

# Disconnect from Azure IoT Hub
client.disconnect()
time.sleep(3)

sleepは適当に入れていて本当はフラグがセットされるまで待つような同期化で実装すべき

■Subscriptの例
Subscribeは以下で動作を確認した。Topicが異なるので折返し試験はできない

#!/usr/bin/python3

# $ python3 -m pip install paho-mqtt

import paho.mqtt.client as mqtt
import ssl
import os
import time

# upkhub02.azure-devices.net

HUB_NAME = "xxxxxx"
DEVICE_ID = "xxxxxx"

# Azure IoT Hub connection settings

ROOT_CA = "./root.ca"
CERT_FILE = "device.crt"
KEY_FILE = "device.key"


def on_connect(client,userdata,flag,rc):
    print("Connected:",str(rc))

# def on_publish(client,userdata,mid):
#    print("Ppublished")

def on_subscribe(client, userdata, mid, granted_qos):
    print("Subscribed:",str(mid))

def on_message(client, userdata, message):
    print("Received message '" + str(message.payload) + "' on topic '"
        + message.topic + "' with QoS " + str(message.qos))

def on_disconnect(client,userdata,rc):
   print("Disconnected:",str(rc))


# MQTT client
client = mqtt.Client(client_id=DEVICE_ID, protocol=mqtt.MQTTv311)
client.on_connect = on_connect
client.on_disconnect = on_disconnect
# client.on_publish = on_publish
client.on_subscribe = on_subscribe
client.on_message = on_message    

hub_address = f"{HUB_NAME}.azure-devices.net"
user_name = f"{hub_address}/{DEVICE_ID}/?api-version=2021-04-12"
client.username_pw_set(username=user_name, password=None)

# SSL/TLS context
ssl_context = ssl.create_default_context()
ssl_context.check_hostname = False
ssl_context.verify_mode = ssl.CERT_NONE
client.tls_set(ca_certs=ROOT_CA, certfile=CERT_FILE, keyfile = KEY_FILE,
               cert_reqs=ssl.CERT_REQUIRED, tls_version = ssl.PROTOCOL_TLSv1_2, 
               ciphers=None)

client.tls_insecure_set(False)

# Connect to Azure IoT Hub

print(f"connect: {hub_address}")
client.connect(host=hub_address, port=8883)
time.sleep(1)


# subscribe message
topic = f"devices/{DEVICE_ID}/messages/devicebound/#"
client.subscribe(topic=topic)


# start the MQTT processing loop
client.loop_forever()


# Disconnect from Azure IoT Hub
client.disconnect()
time.sleep(3)

■追記
Pubishしたつもりの値が正しく受理されているか?はMSが提供しているAzureIoTExplorerを用いて確認する。これはAzureのコンソールからたどるのではなく、別アプリなのであった。インストールして確認すると、以下の通りPublishされているのを確認(東京エレクトロンTNX!)

Tue Mar 07 2023 20:27:58 GMT+0900 (日本標準時):
{
  "body": "Hello from Python?",
  "enqueuedTime": "Tue Mar 07 2023 20:27:58 GMT+0900 (日本標準時)",
  "properties": {}
}

■今後の作業
PCのPythonでは動作確認できたので、これをMicroPythoのMQTTで書き直して、IoTデバイスからAzureIoTにPub/Subできるかを確認する。AWSもClinet証明書による認証でPub/Subできるのを確認しているので、動くだろうとは思うが、、やってみないとわからない。


■参考URL
Azure IoT Hub のドキュメント | Microsoft Learn
IoT Hub に MQTT でメッセージを Publish する際のトピック名について | Japan IoT Support Blog
IoT Hubへ接続するときの認証やキーなど - matsujirushi’s blog
Azure IoT Hub に Mosquitto™ から MQTT なげてみる - Qiita
Azure IoT Hub に mosquitto で接続 - Qiita
Azure IoT Hubを生MQTTS(mosquitto)やHTTP RESTで使う方法 - Qiita
図解 X.509 証明書 - Qiita
Getting Started: Azure IoT Hub と X.509 CA 証明書で認証した接続をする | SORACOM Beam | ソラコムユーザーサイト - SORACOM Users
IoT Hubの設定 | ぷらっとホームどきゅめんと
X.509 証明書を持つデバイスをアプリケーションに接続する - Azure IoT Central | Microsoft Learn
SAS トークンを使用して IoT Hub へのアクセスを制御する | Microsoft Learn
azure-docs.ja-jp/iot-hub-x509ca-overview.md at master · MicrosoftDocs/azure-docs.ja-jp · GitHub
IoTMQTTSample/IoTHubRootCA.crt.pem at master · Azure-Samples/IoTMQTTSample · GitHub
Understand Azure IoT Hub MQTT support | Microsoft Learn
https://cptechweb.teldevice.co.jp/hc/ja/articles/900007074643--%E5%88%9D%E5%BF%83%E8%80%85%E5%90%91-Azure-IoT-%E3%82%B7%E3%82%B9%E3%83%86%E3%83%A0%E6%A7%8B%E7%AF%89-4-IoT-Hub%E3%81%AE%E5%8F%97%E4%BF%A1%E7%A2%BA%E8%AA%8D

*1:所有証明って、自分がルートCAの秘密鍵を持っていて証明書に署名できることを示す工程なのか?