問題点
ESP8266でMilkcocoaを使うためのライブラリとしてMilkcocoa ESP8266 SDKがあります。
たいへん分かりやすく使いやすいライブラリだけども、同期処理(ブロッキング処理)なのが困りものです。下のコードを見てください。
loop(){ // 以下をloopの中で必ず実行します milkcocoa.loop(); // ここに1秒間もとどまる // 以下の処理が回らない (略) }
このようにloop()の中で、必ずMilkcocoa::loop()を実行する必要があります。そして、このMilkcocoa::loop()は、一度入るとタイムアウトまで最大1秒間も戻って来ません。これは本来、組込み系ではやってはいけないことです。loop()が止まってしまうので、その間は他の処理を進めることができません。
問題の原因箇所
それでは、Milkcocoa ESP8266 SDKのコードを追ってみます。
関数の呼び出し階層は以下のようになります。
- Milkcocoa::loop()
- Adafruit_MQTT::readSubscription()
- Adafruit_MQTT_Client::readPacket()
そしてAdafruit_MQTT_Client::readPacket()の中で、delay()を繰り返してタイムアウトまで待っています。ここが原因箇所です。
原因の解消案
何もせずにdelay()で待つから処理が止まるわけです。スレッドが使えれば並行処理できますが、Arduinoはシングルスレッドです。C#のような強力な非同期処理機構もありません。
そこで、苦し紛れのハックですが、MQTT_Client::readPacket()でdelay()を呼ぶ代わりにユーザー定義の関数を呼び返してやることにします。呼び返す関数の名前はsubloop()とでもします。ユーザーはスケッチの中でsubloop()関数を定義します。
解消案の実装
修正するライブラリ側のソースは1つだけです。
Adafruit_MQTT_Client.cpp
(略) extern void subloop(); // スケッチ側で定義 uint16_t Adafruit_MQTT_Client::readPacket( (略) //delay(MQTT_CLIENT_READINTERVAL_MS); // 待たない subloop(); // スケッチを呼び返す } return len; } (略)
スケッチ
loop(){ // 以下をloopの中で必ず実行します milkcocoa.loop(); (略) } subloop(){ // Milkcocoaの待ち中に繰り返したい処理をここに }