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


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

プログラム開発には、いつものように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を機能させておきたいところです。そこで、以前の記事、

のように、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シリコン版ならば、動作するようです。
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メッセージで動くアクセサリを実装できます。

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に使っていこうと思います。
コメント