Azure IoT Hub Java SDK 的行為

zonble
5 min readJun 26, 2022

最近工作上需要用到 Azure IoT Hub 的 Java SDK 搭建服務。實際做什麼應用就不說了,總之,我們在 一一些終端裝置上,透過微軟提供的 Java SDK 透過 MQTT 協定連接 IoT Hub,然後讓這些 Android 裝置接收來自 IoT Hub 的 C2D (Cloud-to-Device) 訊息,在收到訊息的時候做一些指定的動作。

因為公司的系統安全政策,在終端裝置與 IoT Hub 的主機,需要加上一個 proxy,終端裝置都只能連接這個 proxy,這個 proxy 再透過內網連接 IoT Hub:也就是說,在使用 IoT Hub Java SDK 開始連線的時候,在連線時所使用的 connection string 中,不但需要有 host name,還需要 gateway host name 這段設定。

在開發初期,由於 proxy 還沒做好,所以,還沒有加上 gateway 這一段,這時候一切正常,每台裝置都可以正常收到 C2D 訊息;接上 gateway 之後,就出現靈異現象了。

明明 MQTT 連線沒有任何的錯誤訊息,但是有些裝置收得到訊息,有些又收不到,而到底哪些裝置能收到訊息、或反之,也看不出規律。更奇怪的是,如果我們去 Azure 的後台 portal 網頁,從網頁上設定要傳給某些設備一些訊息,如果這些設備還沒連上,後台網頁會顯示被 queue 住的訊息數量,如果一連上,訊息數量會歸零,但是這些設備卻沒有收到任何訊息,感覺像是這些訊息憑空蒸發了。

於是進入了工程師通靈時間,大概有三個工程師六隻眼睛下來看這個奇怪的問題。既然訊息會歸零,那麼,或許是 MQTT 的確把訊息傳了過來,但是因為什麼條件,IoT Hub Java SDK 把訊息丟掉了,所以我們先把 debugger 的 break point 設在接收 MQTT 這段 — 果然沒收到,再打開個 Wire Shark 來看看,的確沒有收到任何的封包。如果把 connection string 裡頭的 gateway 設定拿掉,突然又一切正常,什麼訊息都能收到了。

這時候大概有三個工程師、六雙眼睛盯著 IoT Hub Java SDK 看,然後有位同事看到裡頭有幾段看起來是關鍵的 code。第一段是在 MqttIotHubConnection 中,建立 MQTTMessaging 物件的時候,如果 connection string 裡頭有了 gateway host name 的設定,就會將一個叫做 isEdgeHub 的參數設定為 true。

this.deviceMessaging = new MqttMessaging(
deviceId,
this,
moduleId,
this.config.getGatewayHostname() != null && !this.config.getGatewayHostname().isEmpty(),
connectOptions,
unacknowledgedSentMessages,
receivedMessages);

而 isEdgeHub 會影響是否訂閱 MQTT 的 topic。這段寫在 MQTTMessaging 裡頭

if (!this.isEdgeHub)  {
this.subscribe(this.eventsSubscribeTopic);
}

也就是說,如果設了 gateway host name,就會造成被 SDK 當成是 edge hub,而 edge hub 是不會去訂閱 MQTT Topic 的,於是收不到來自 MQTT 的訊息。但實際上遇到的行為卻非常難懂—既然沒有 MQTT topic,所以終端設備沒有收到消息,那為什麼微軟又會在 MQTT 連線連上的時候,把訊息清空呢?然後,如果你把 gateway host name 拿掉,成功收到 MQTT 訊息一次,那麼,之後就算把 gateway host name 加回去,一樣可以收到訊息。為什麼啊…。

跑去 GitHub 問了微軟的人,得到的答覆是,當初 gateway host name 的使用方式,就跟我們這次的使用情境不同。微軟的設計是,在一台 IoT Hub 裝置後面,還可以有多台的 IoT Hub 裝置,這種「在裝置後面的裝置」就是 Edge Hub,對於 Edge Hub 來說,前面的這台裝置就是 gateway,而不是像我們這樣,在前面加個 proxy。對微軟來說,C2D 訊息只要傳到最前面的那台裝置就好,後面的 Edge Hub 根本就不需要送。但既然不要送,那為什麼訊息還被清空了?微軟也沒給一個清楚的回答,總之,這就是個既有行為,可是如果去改了這個行為,又會是個 breaking change。

我們可以來看一下微軟的文件 How an IoT Edge device can be used as a gateway,裡頭提到,微軟把裝置分成四種:IoT device 、IoT behind a gateway、IoT Edge device、IoT Edge behind a gateway;我們一開始以為,如果加上了 gateway host name,應該會變成 IoT behind a gateway 吧?但實際上變成了 IoT edge device,所以,按照文件,就收不到 C2D 訊息了。

所以這四種裝置的 connection string 應該這樣設

  • Iot device:host name 直接設成微軟 IoT Hub 的 host name
  • IoT behind a gateway:把 host name 設成你的 proxy 的所在位置
  • IoT Edge device:connection string 中同時要有 host name 與 gateway host name
  • IoT Edge behind a gateway:…我不知道。話說 IoT Edge device 不也就在一個 gateway 後面了嗎?

--

--