ESP-01Sが安かったのでHomeKitで遊んでみる

DIYする

以前の記事では、I2C接続の温度湿度センサDHT20をRaspberry PiやESP32に接続し、HomeKitから使えるセンサを作りました。

温度湿度センサDHT20をRaspberry Piに接続してHomeKitから使う
秋月電子で売られている温度湿度センサDHT20(380円) をRaspberry Pi 4に取り付けました。これから温度・湿度を取り込み、MQTTに流すPythonプログラムを書きました。それをHomebridgeで受け取ってHomeKit...
温度湿度センサDHT20をESP32に接続してHomeKitから使う
秋月電子で380円で販売されているI2C接続の温度湿度センサDHT20を、前回はRaspberry Piに接続しました。今回はこれのESP32版です。このセンサを、ESP32に接続します。そして取得した温度、湿度のデータを、今回もMQTTメ...

今回は、安かったので買い込んだESP-01Sにに、DHT20を接続します。ESP-01, 01Sは、EspressifのWi-Fi搭載マイコンチップESP8266を搭載した開発ボードです。ESP8266に加えて、アンテナ、クロック、フラッシュメモリなどが搭載されてます。ESP-01、ESP-01Sの詳細は以下をご覧ください。

ESP8266 (ESP-01) をArduino IDEで使う(1: Lチカ編)
これまで、ESP32を使ってHomeKitで動作するアクセサリをDIYしてきました。ESP32の下位モデルにESP8266があります。シンプルなアクセサリならばESP8266でも作れますし、市販のスマートプラグ、スイッチ、センサー類にはES...

プログラム開発には、いつものようにArduino IDEを使いましたので、以前のESP32の記事とほとんど同じです。なので異なる部分を中心に説明しておきます。

ESP-01Sを191円で買う

少し前はESP32をDIYに使ってましたが、最近はESP-01を使ってます。スマートホームのセンサやスイッチ用途ならば、GPIOが4本もあれば大抵困りません。なのでもう少し買っておこうと思い、AliExpressを探しました。

調べたところ、ESP-01もESP-01Sも、ほぼ同じ値段で売られてます。過去のDIYではESP-01を使っていたので、今度はESP-01Sを使ってみようと思いました。お得なのでESP-01Sの10個セットを買ってみました。AliExpressだと、EPS-01もEPS-01Sもどちらも送料込みで10個1,910円、一個当たり191円です。一方で、AmazonだとEPS-01が送料込み10個2,585円でした。いずれにしても、Wi-Fi搭載したコンピュータで、この価格は安いです。なお、基板にはESP-01Sという文字は刻印されていません。オリジナルはAI-Thinkerという会社の製品だそうですが、それとは違う、互換製品のようです。

今回1個使用したので残り9個です

たくさん買えたので、遊んでみたのが今回の記事です。なお最初にプログラムを書き込むためには、USBアダプターも必要です。詳細はこれも以前の記事をご覧ください。

Lチカする

まずはLEDを点滅させます。USBアダプタでMacに接続して、以下のプログラムをArduino IDEからダウンロードしました。

const int BlueLED=2; //Onboard blue LED (ESP-01S)
//const int BlueLED=1; //Onboard blue LED (ESP-01)

void setup() {
  pinMode(BlueLED, OUTPUT); // Initialize the LED pin as an output
}

void loop() {
  digitalWrite(BlueLED, LOW); // Turn the LED on
  delay(100); // Wait for 100ms
  digitalWrite(BlueLED, HIGH); // Turn the LED off
  delay(900); // Wait for 900ms
}

ESP-01とESP-01Sは、以下のように基板上の配線が異なります。

https://www.forward.com.au/pfod/ESP8266/GPIOpins/ESP8266_01_pin_magic.html より引用

https://www.forward.com.au/pfod/ESP8266/GPIOpins/ESP8266_01_pin_magic.html より引用

違いをまとめると、以下のようになります。

項目 ESP-01 ESP-01S
電源LED 有り(赤色) 無し
青色LED TX(GPIO1)に接続 GPIO2に接続
CH_PD 配線なし 12kΩでプルアップ
GPIO0 配線なし 12kΩでプルアップ
GPIO2 配線なし LEDでプルアップ

ESP-01Sには、GPIO2に青色LEDが付いていますので、上記のプログラムでLチカできます。ESP-01無印の場合は、プログラム冒頭のコメントの行を生かして、TX(GPIO1です)を出力に設定すると、青色LEDが点滅します。

ESP-01, 01Sをプログラム動作モードで起動するためには、電源投入直後に、CH_PD, GPIO0, GPIO2がHighになる必要があります。ESP-01Sでは、これらのピンがプルアップされているので、単体で動作します。実際に、ESP-01SをUSBアダプタから取り外して、直流安定化電源から3.3Vを供給したところ、単体で引き続きLチカしました。

ESP-01Sはこのようにすぐに動作しますが、ESP-01の場合は、以前の記事で紹介したように、CH_PDをプルアップする必要があるはずです。

OTAを動かす

ESP-01, 01SをUSBアダプターから外すと、USB経由ではプログラムを更新できなくなってしまいます。そこでArduinoOTAを機能させておきたいところです。そこで、以前の記事、

Arduino IDEからESP32をOTAアップデート(改訂版)
OTA (Over The Air)を使ってESP32のスケッチをアップデートする方法の改訂版です。以前、試したESP32のスケッチ例は、もっと簡単にしても良いことを他の方のブログで知りました。おかげさまで活用できるようになりました。という...

のように、ArduinoOTAを動かしました。また、この後の実装で、MQTTクライアントが必須なので、Wi-Fi接続はMQTTのライブラリに任せることにしました。以下が、Lチカをしつつ、OTAにも対応したプログラムです。ただしOTAは動作しますが、MQTTの部分は実装途中で、意味のある動作はしません。

#include <EspMQTTClient.h>
#include <ArduinoOTA.h>

EspMQTTClient *client; //pointer to MQTT instance
const char SSID[] = "XXXXXXXX"; //WiFi SSID
const char PASS[] = "xxxxxxxx"; //WiFi password
char CLIENTID[] = "ESP32_xx:xx:xx:xx:xx:xx"; //MAC address is set in setup()
const char MQTTADD[] = "192.168.xxx.xxx"; //Broker IP address
const short MQTTPORT = 1883; //Broker port
const char MQTTUSER[] = "xxxxxxxx";//Can be omitted if not needed
const char MQTTPASS[] = "XXXXXXXX";//Can be omitted if not needed

const int blueLED=2;

void setup() {
  pinMode(blueLED,OUTPUT);
  String wifiMACString = WiFi.macAddress(); //WiFi MAC address
  wifiMACString.toCharArray(&CLIENTID[6], 18, 0); //"ESP32_xx:xx:xx:xx:xx:xx"
  client = new EspMQTTClient(SSID,PASS,MQTTADD,MQTTUSER,MQTTPASS,CLIENTID,MQTTPORT);
  delay(1000);
}

void onConnectionEstablished() {
  ArduinoOTA.setHostname("ESP8266_OTA");
  ArduinoOTA.setPasswordHash("99999999999999999999999999999999");
  ArduinoOTA.begin();
}

static int counter=0;
void loop() {
  ArduinoOTA.handle(); //handle OTA
  client->loop(); //handle MQTT
  delay(100);
  counter++;
  if(counter==1) digitalWrite(blueLED,LOW); //turn on the LED
  if(counter==2) digitalWrite(blueLED,HIGH); //turn off the LED
  if(counter>=10) counter=0;
}

ArduinoOTAが動作していれば、Arduino IDEウィンドウ上のポート選択メニューから、Wi-Fiポートを選択できるはずです。

OTAトラブル対策

ArduinoOTAを使う際に、注意すべき点がありました。前回のファンヒーターDIYの時に気づいていたのですが、ここでまとめておきます。

ネットワークポートが現れない

ArduinoOTAが提供するネットワークポートが、Arduino IDEから見えないことがあります。その場合は、ESP-01, 01Sを再起動することで解決するようです。

ネットワークポートの様子を確認するには、ポート選択メニューの一番下の「Select other board and port…」メニューを選択すると便利でした。

すると、ボードとポートを選択するためのダイアログパネルが表示されます。

ボードは、Generic ESP2866 Moduleが選択されていることを確認しておきます。選択が外れていたら、BOARDSの項目で選択します。

PORTSの側にArduinoOTAが用意しているポートが見えているようなら、それを選択するのですが、ネットワークポートが現れないことも多いです。その場合、ESP-01Sの電源をOff/Onして再起動させると、10秒程度でポートが現れます。現れない場合は、再起動を再び試すと良いです。ArduinoOTAではmDNS (micro DNS) を使ってアドレスを通知しているようなのですが、これが動作しないことがあるのでは無いかと推測してます。

ポートにデータ転送できない

ArduinoOTAのネットワークポートを選択できていて、コンパイルはできるのですが、その後のデータ転送でエラーが出て失敗することがあります。以下のようなエラーメッセージが出ます。実際には1行なのですが、長いので改行を入れています。

File "~/Library/Arduino15/packages/esp8266/hardware/esp8266/3.1.2/tools/espota.py", line 103, 
in serve sock2.sendto(message.encode(), remote_address) 
OSError: [Errno 65] No route to host 
Failed uploading: uploading error: exit status 1

Pythonのプログラムの中で、エラーが出て停止しているようです。プログラムを確認してはいないのですが、変数名も考慮すると、ソケット通信でデータを送信しようとしていたところ、ホストへのrouteが見つからないというエラーのようです。これは2台のインテル版macOSで確認しました。一方で、Appleシリコン版macOSや、WindowsでArduino IDEを動かしたところ、このエラーは出ず、正常にOTAできました。ESP32を使っていた頃は、インテル版macOSでもArduinoOTAが可能だったので、謎です。ESP8266のコードがシステムのバグを引き当ててしまったのかもしれません。以後は、M1搭載のMacBook Airで作業することにしました。

エラーメッセージで検索したところ、同様に、インテル版macOSでのみこのエラーが出るという話題が見つかりました。Appleシリコン版ならば、動作するようです。

OSError: [Errno 65] No route to host · Issue #658 · doronz88/pymobiledevice3
I have this problem where I keep getting OSError: No route to host when creating tunnels just on intel-based macs, the M...

macOSかPythonライブラリのバグなのかもしれませんし、将来のアップデートで解消されるかもしれません。インテル版macOSが終了間近なので、そろそろメインマシンをインテルからAppleシリコンに移行すべきなのかもしれません。

回路を作る

次に、DHT20と接続する回路を作りました。回路と言っても、I2Cの2本の線と、電源(3.3V)とGNDを配線するだけです。SDAとSCLは、それぞれGPIO0とGPIO2に接続しました。調査してくださった人の情報によると、ESP8266の全てのGPIOでI2C通信が可能らしいです。なので、GPIO3やGPIO1も使用可能なようです。

ブレッドボード上で配線した様子です。DHT20の足は弱いので、曲げないように慎重にブレッドボードに挿します。

DHT20ライブラリを使う

ESP32の時と同様にRob版のDHT20ライブラリを使うことにしました。詳細は、以前の記事をご覧ください。ただし、以前の記事では、I2Cに接続するGPIOピンとして、デフォルトのまま21番と22番ピンを使いました。そのため、ライブラリのDHT20クラスのコンストラクタDHT20()も引数無しで簡単でした。以下にその部分を示します。

#include <DHT20.h>
DHT20  *dht; //DHT20 instance

void setup(){
  dht = new DHT20();
  dht->begin(); //use default GPIO: 21,22
  
 //略
}

今回は、を0番と2番のGPIOピンを使用するので、デフォルトから変更する必要があります。ESP8266には、そもそも21,22番ピンは無いらしいので、いずれにしても変更する必要があります。その方法を探すためにDHT20のソースを調べましが、GPIOピンを直接設定するメソッドはありませんでした。その代わりに、DHT20の引数付きコンストラクタとして、TwoWireというクラスのインスタンス(へのポインター)を引数とするコンストラクタがありました。以下は、DHT20クラスのヘッダファイルであるDHT20.hからの抜粋です。

class DHT20
{
public:
  //  CONSTRUCTOR
  DHT20(TwoWire *wire = &Wire);
略
}

DHT20.cppのソースを見ると、コンストラクタで指定されたTwoWireインスタンスのメソッドを駆使して、DHT20にアクセスしています。ということで、TwoWireというクラスは、I2C関連のクラスのようです。

そのように見当つけて探したところ、Wire.hというArduinoのライブラリがI2Cを担当していて、その中でTwoWireクラスが定義されていることがわかりました。TwoWireクラスのbeginメソッドで、I2Cに使用するGPIO番号も指定できるようです。そこで、上記のコードの部分を、以下のようにして対応することにしました

#include <Wire.h>
#include <DHT20.h>
const int pinSDA=0; //GPIO pins for I2C
const int pinSCL=2;
TwoWire *dht_wire; //pointer to I2C instance
DHT20 *dht; //pointer to DHT20 instance

void setup(){
  dht_wire = new TwoWire();
  dht_wire->begin(pinSDA,pinSCL); //assign GPIO
  dht = new DHT20(dht_wire); //use custom GPIO
  dht->begin(); 
  
 //略
}

最初にTwoWireのインスタンスdht_wireを作って、それにGPIOピン番号を割り当てて、このdht_wireを引数として、DHT20コンストラクタを呼び出してます。

MQTTにパブリッシュする

HomeKitとの接続は、今回もMQTTメッセージを使います。センサ情報は下図の左から右に流れます。MQTTブローカとHomebridgeがLAN内のRaspberry Pi 4で稼働していて、これがESP-01SからのMQTTメッセージを受けて、iPhoneやMacのあるHomeKit側に公開します。

ここまでのプログラミングで、DHT20にアクセスして、温度、湿度データを得られるようになりました。そこで、30秒毎に温度湿度データを得て、MQTTブローカーパブリッシュする機能を、以下のように実現しました。これで、温度湿度センサーが完成です。DHT20に接続するGPIO線をデフォルト値(21と22)から変更した点以外は、以前のプログラムと同様です。

#include <EspMQTTClient.h>
#include <ArduinoOTA.h>
#include <Wire.h>
#include <DHT20.h>

EspMQTTClient *client; //pointer to MQTT instance
const int pinSDA=0; //pins for I2C
const int pinSCL=2; //pins for I2C
TwoWire *dht_wire; //pointer to I2C instance
DHT20 *dht; //pointer to DHT20 instance

//WiFi & MQTT
const char SSID[] = "XXXXXXXX"; //WiFi SSID
const char PASS[] = "xxxxxxxx"; //WiFi password
char CLIENTID[] = "ESP32_xx:xx:xx:xx:xx:xx"; //MAC address is set in setup()
//for example, this will be set to "ESP32_84:CC:A8:7A:5F:44"
const char MQTTADD[] = "192.168.xxx.xxx"; //Broker IP address
const short MQTTPORT = 1883; //Broker port
const char MQTTUSER[] = "xxxxxxxx";//Can be omitted if not needed
const char MQTTPASS[] = "XXXXXXXX";//Can be omitted if not needed
const char SUBTOPIC[] = "mqttthing/esp01S/set"; //mqtt topic to subscribe
const char PUBTOPIC[] = "mqttthing/esp01S/get"; //mqtt topic to publish
const char PUBDEBUG[] = "mqttthing/esp01S/debug"; //for debug message

void onConnectionEstablished() {
  ArduinoOTA.setHostname("esp01S");
  ArduinoOTA.setPasswordHash("99999999999999999999999999999999");
  ArduinoOTA.begin();
  client->subscribe(SUBTOPIC, onMessageReceived); //set callback function
  client->publish(PUBDEBUG,"ESP01S temp/humi sensor started.");
  publishDHT();
}

void onMessageReceived(const String& msg) {
  client->publish(PUBDEBUG,"Message received.");
  publishDHT();
}

void setup() {
  String wifiMACString = WiFi.macAddress(); //WiFi MAC address
  wifiMACString.toCharArray(&CLIENTID[6], 18, 0); //"ESP32_xx:xx:xx:xx:xx:xx"
  client = new EspMQTTClient(SSID,PASS,MQTTADD,MQTTUSER,MQTTPASS,CLIENTID,MQTTPORT);
  dht_wire = new TwoWire();
  dht_wire->begin(pinSDA,pinSCL);
  dht = new DHT20(dht_wire);
  dht->begin();
  delay(1000);
}

void publishDHT() {
  char buff[64];
  float humi, temp;
  if(DHT20_OK != dht->read()){
    client->publish(PUBDEBUG,"DHT20 Read Error.");
  }else{
    humi=dht->getHumidity();
    temp=dht->getTemperature();
    sprintf(buff, "{\"temperature\":%.1f,\"humidity\":%.0f}", temp, humi);
    client->publish(PUBTOPIC,buff);
  }
}

void loop() {
  ArduinoOTA.handle(); //for OTA
  client->loop(); //for MQTT
  if(millis() - dht->lastRead() >= 30000) publishDHT(); //30sec
}

別のターミナルウィンドウでmosquitto_subコマンドを動かし、サブスクライブして動作確認しました。結果は以下のようになりました。

mqttthing/esp01S/get {"temperature":25.0,"humidity":43} mqttthing/esp01S/get {"temperature":25.1,"humidity":43} mqttthing/esp01S/get {"temperature":25.0,"humidity":43}

今回もおまけの機能として、setというトピックに何かメッセージを送ると、次の3分を待たずにすぐに結果を返してくれるようにしてみました。Zigbeeセンサで見かけた機能です。ターミナルから、


% mosquitto_pub -t "mqttthing/esp01S/set" -m ""

などすると 、すぐに

 mqttthing/esp01S/get {"temperature":25.1,"humidity":42}

のように、結果を返します。測定結果がすぐに必要なアプリで役立つのではと思いました。

Homebridgeで受け取る

これも前回Raspberry PiやESP32にDHT20を取り付けた時と同じです。HomebridgeにはMqttthingプラグインを入れてあります。MQTTメッセージで動くアクセサリを実装できます。

公衆MQTTサーバでLチカする
スイッチやセンサの状態や、リレーやサーボモータの制御コマンドを送受信する目的で、MQTT (Message Queueing Telemetry Transport)というプロトコルが使われます。MQTTは、Home AssistantやH...
GitHub - arachnetech/homebridge-mqttthing: A plugin for Homebridge allowing the integration of many different accessory types using MQTT.
A plugin for Homebridge allowing the integration of many different accessory types using MQTT. - arachnetech/homebridge-...

Mqttthinの設定から、tenmeratureSensorとhumiditySensorを使います。MQTTの設定は、プラグインの設定画面からGUIで入力できます。最終的に出来上がったconfigの部分は以下になりました。

{
  "type": "temperatureSensor",
  "name": "DHT20_temp",
  "username": "xxxxxxxx",
  "password": "XXXXXXXX",
  "topics": {
    "getCurrentTemperature": "mqttthing/esp01S/get$.temperature"
  },
  "accessory": "mqttthing"
},
{
  "type": "humiditySensor",
  "name": "DHT20_humi",
  "username": "xxxxxxxx",
  "password": "XXXXXXXX",
  "topics": {
    "getCurrentRelativeHumidity": "mqttthing/esp01S/get$.humidity"
  },
  "accessory": "mqttthing"
}

HomeKitから使ってみる

この結果、iPhoneやMacのホーム.appの上に、

というような表示が現れ、これをクリックすると、

のように、今回取り付けたセンサーの値が表示されました。

技適について

検索すると「ESP8266は技適が無いので国内では使ってはいけない」という記述がいくつか見つかります。でも、総務省の「技術基準適合証明等を受けた機器の検索」ページで、ESP-01やESP8266を検索すると、複数の登録があります。例えば、こちらのページには、ESP8266が「相互承認(MRA)による工事設計認証」を受けていると書かれてます。

MRAは、海外で認証試験したものを日本でも認める仕組みです。今回の製品そのものには技適マークはついていないので、適合しているとは言い切れませんが、他に迷惑をかけるような電波は出していないと思われます。

なお、ESP8266の技適について調べた方のページによると、「モジュールが小さいなどで読み取れない場合は、取説や包装に表示する措置が認められている」そうです。なので、チップ本体に技適マークがついていないからといって、不適合というわけでも無いようです。

まとめ

AliExpressで、単価191円の安価なESP-01Sを10個買いました。これをI2C温度・湿度センサDHT20に接続して、iPhoneやMacのホーム.appから利用できるようにプログラムしました。I2Cも問題なく使えました。ESP-01, 01Sは小型で安価なので、DIYに使っていこうと思います。

コメント

タイトルとURLをコピーしました