2021年1月6日水曜日

ESP32-POE-ISOとNETGEAR GS305Pで配線すっきり

 ESP32とW5500で有線LANで接続することはできました。ただ、これだと電源とLANケーブルの2本を引きずり回すことになってちょっと面倒です。

実はPoEでネットワーク経由で電源も供給できれば1本で取り廻すことができるなというのは以前から考えていました。ESP32をPoE化している方もネットでは見受けられましたが、できれば製品として提供されている方が現場で利用する場合にも簡単・安心です。

ネットを検索したところESP32-POE-ISOという製品が見つかったので早速取り寄せてみました。

ESP32-POE-ISO仕様


仕様について詳細は英文ではありますがこちらを参照していただければと思います。
概要としては
  • ESP32-WROOM-32
  • Micro USBコネクタ(ESP32-DevKiCと同様にプログラムのアップロードもこれでOK)
  • MicroSD card(GPIOを3つ使用、カードスロットは背面に装着済)
  • LiPo battery(コネクタ有)
となかなかの優れものです。
LiPo batteryが接続できるので、PoEで充電しつつ電源断(=ネットワーク断)となった場合にはLiPo batteryから電源を取ってMicro SDカードにデータを保存しネットワークが復旧したら(電源も復旧になりますね)その間のデータをアップロードするといったシステムもこれ一つで構築できます。

ただ欠点は機能が豊富な分、ユーザが利用できるGPIOの数が少なくなっているという点でしょうか。利用できるGPIOは以下の通りです。

EXT1
  • 5V
  • 3.3V
  • GND
  • ESP_EN
  • GPIO0
  • GPIO1
  • GPIO2(MicroSD cardで使用)
  • GPIO3
  • GPIO4
  • GPIO5
EXT2
  • GPIO39(入力専用)
  • GPIO36(入力専用)
  • GPIO35(入力専用)
  • GPIO34(入力専用)
  • GPIO33
  • GPIO34
  • GPIO16
  • GPIO15(MicroSD cardで使用)
  • GPIO14(MicroSD cardで使用)
  • GPIO13

開発環境


VSCode + PlatformIOで開発していますが、ボードとして「OLIMEX ESP32-PoE-ISO」を指定可能になっています。サンプルプログラムも標準的なものは用意されているので、サンプルを参考に開発も簡単にできそうです。

サンプルはESP32-POE用ですが、ESP32-POEとESP32-POE-ISOの違いはアイソレーション(insulation:絶縁)がされているか、いないかです。この辺りは電気的なお話なので私も詳しくは理解していませんが、ハブ側とESP32側が電機的に直接接続されているか、間接的なのかの違いです。安全性の面から絶縁されていた方が良いのですが、ESP32-POEを使うときはそのあたりは自分で考えてねということになります。

さて、platformio.iniは

[env:esp32-poe-iso]
platform = espressif32
board = esp32-poe-iso
framework = arduino
monitor_speed = 115200

プログラムはESP32_PoE_Ethernet_Arduinoを参考にすればOKです。
使用しているETHはWiFiをベースに開発されているのでESPmDNSがそのまま利用できます。

            // Get server ip address by mDNS
            if (!MDNS.begin(client_name)) {
                Serial.println("Error setting up MDNS(ETH) responder!");
                while(true) { 
                    delay(1000);
                }
            }
            mqtt_server_address = MDNS.queryHost(MQTT_SERVER);

            Serial.print("Server");
            Serial.print("(");
            Serial.print(MQTT_SERVER);
            Serial.print(") IP address : ");
            Serial.println(mqtt_server_address);

プログラムを書込み後、USB接続を外してから、この為に購入したNETGEAR GS305Pに接続すると問題なくサーバ側でデータを受信することができました。

これからの目論見


ESP32を
  1. Wi-Fiでネットワークに接続+AC電源(一般的な利用方法)
  2. 有線LANでネットワークに接続+AC電源(Wi-Fiが不安定な場合)
  3. PoEでネットワークに接続&電源
という3種類の設置方法が確認できました。

現在製造業におけるIoTの導入支援をお手伝いしていますが、サーバー側を含めて標準的なベースシステムを作っていければなと考えています。ある程度形になった所でソースも公開したいと思いますのでしばらくお待ちください。

2021年1月5日火曜日

ESP32をW5500で有線LAN接続してmDNSしてみた

 最近はESP32で遊んでいますがWi-Fiの環境が不安定な場合、有線LANで接続できるとうれしいかなということでW5500を積んだボードを見つけて早速やってみました。

ESP32にセンサーをつないでMQTTでサーバへデータを投げるんですが、Wi-Fiでつなげた場合はmDNSを使ってサーバーのhostnameで接続できるんですが、有線LANでちょっとトラブったので覚書としてアップしておきます。

開発環境


Windows10
VSCode + PlatformIO
ESP32-DevkitC

platformio.iniは
[env:esp32doit-devkit-v1]
platform = espressif32
board = esp32doit-devkit-v1
framework = arduino


有線LANの接続


W5500についてはAmazonでW5500イーサネット ネットワークモジュールを購入しました。W5500は3.3V駆動なんですがこのボードは5Vでも動くようにDCDCコンバータが搭載されているのが特徴でしょうか。
  • 5V = 5V
  • GND = GND
  • RST = GPIO4
  • MISO = GPIO19
  • MOSI = GPIO23
  • SCS = GPIO5
  • SCLK = GPIO18 

ネットで検索するとESP32でW5500を使って有線LANを行っている記事は多数みうけられますが、基本はSPI接続をすればライブラリがあるのでそれ程戸惑うことはないと思います。
ポイントとしてはEthernetライブラリではなくEthernet2ライブラリを使用することでしょうか。
後、多くの記事ではリセットピンについては接続していないようですが、ESP32がリセットされた場合にW5500側もリセットした方がいいかなと思い接続してみました。

プログラムはこんな感じです。
    byte mac[] = { 0xDE0xAD0xBE0xFE0xFE0x01 };    
    pinMode(4OUTPUT);
    digitalWrite(40);
    delay(25);
    digitalWrite(41);
    delay(500);
    Ethernet.init(5);
    Ethernet.begin(mac);
    Serial.print("IP address: ");
    Serial.println(Ethernet.localIP());

後はMQTTライブラリでサーバに接続すれば完了です。


mDNSでサーバーのIPアドレスを取得する


ここまでは問題なかったのですが、Wi-Fiで接続しているときはサーバーのIPアドレスはESPmDNSを利用して取得していました。問題はESPmDNSはWi-Fi接続を前提として作成されているのでW5500(Ethernet2 )では利用できません。
そこでW5500でmDNSを利用できるMDNS_Genericを使用することにしました。
PlatformIOでライブラリをインストールすればサンプルにResolvingHostNamesもあるので簡単かなと・・・

まずはREADMEのLibraries' Patchesの「4. To fix Ethernet2 library」の指示に従いEthernet2のソースにパッチ(ファイルの置き換えと追加)を当てます。
後はサンプルを参考にソースをちょいちょいと作成してコンパイルすると「beginMulticast」の未定義エラーになってしまいました。
ソースを眺めてみると確かにパッチを当てたEthernetUdp2.cppにbeginMulticastはあります。

問題はMDNS_Generic.hにありました。

MDNSクラスのプライベート変数として_udpがUDPクラスの変数として定義されていますが、beginMulticastはEthernetUDP2.hで定義されているEthernetUDPクラスのメソッドとして定義されています。サンプルでもEthernetUDPクラスのインスタンスをMDNSのコンストラクタで渡しています。EthernetUDPクラスはUDPクラスを継承しているのでエラーになりませんが、beginMulticastメソッドを使うところでUDPクラスでは未定義になってしまいます。

ということでMDNS_Generic.hの以下の2か所の修正を行います。

class MDNS
{
  private:
    EthernetUDP*              _udp;

  public:
    MDNS(EthernetUDP& udp);

さらにMDNS_Generic_Impl.hも以下の2か所の修正を行います。

//#include <Udp.h> <-コメントアウト
#include <EthernetUDP2.h>

MDNS::MDNS(EthernetUDPudp)
{

これで無事にコンパイルは通りました。


hostnameの長さが固定?


コンパイルも無事に通り実行してみると、なぜかリブートを繰り返します。
仕方がないのでソースを眺めてみると、検索に失敗した場合に発生しているようです。

検索結果を返すコールバック関数を呼び出すメソッド_finishedResolvingNameメソッドでアドレス例外が発生しているようです。検索できなかった場合、IPアドレスがNULLで_finishedResolvingNameが呼び出されるのですが、それが原因でした。文字列のIPアドレスをIPAddressクラスのインスタンスに変換してコールバックを呼び出していますが、ここで文字列がNULLなのでアドレス例外が発生していました。

    this->_nameFoundCallback((const char*)nameipAddr == NULL ? INADDR_NONE : IPAddress(ipAddr));

これでリブートは収まりましたがまだ検索できていないようです。

    #define _MDNS_LOGLEVEL_ 4
    #include <Ethernet2.h>
    #include <MDNS_Generic.h>

とするとデバッグログをSerialに出力してくれるのでログを確認すると、アドレス解決ははできているようですが、なぜか解決不可となってしまいます。

またソースを見る羽目に・・・
本当にデバッグしてあるのかな?

                  //KH, to report name Resolve only UDP packet has corect size of 48
                  if (48 == udp_len)
                  {
                    // KH debug
                    MDNS_LOGINFO1("::_processMDNSQuery: to report IP, buf ="String((uint8_tbuf[0]));
                  
                    this->_finishedResolvingName((char*)this->_resolveNames[0], (const byte*)buf);
                  }

とudp_lenが48バイト固定の判定している部分が原因でした。
応答のUDPパケットのサイズを48バイト固定にしているので、hostnameの長さが14バイト(ドメインは.localで固定)でないと駄目なようです。

ということでパケットサイズのチェックは以下のように修正しました。

                  uint16_t l = sizeof(DNSHeader_t) + (strlen((const char *)this->_resolveNames[0])+2) + 4 + 6 + dataLen;
                  if (l == udp_len)
                  {
                    // KH debug
                    MDNS_LOGINFO1("::_processMDNSQuery: to report IP, buf ="String((uint8_tbuf[0]));
                  
                    this->_finishedResolvingName((char*)this->_resolveNames[0], (const byte*)buf);
                  }


ちなみにdataLenはIPアドレスの長さですが、これも4バイト固定(この部分の少し上でチェックしていました)になっているのでIPv4限定となります。いまのところIPv4も割り当てているのでこれで良しとしました。

Edisonが故障した一件

 最近またEdisonで遊んでいます。 教育用のデバイスとしてよく出来ているなと思っている Edison ですが、現在3台を所有しています。 最近このうちの1台が調子が悪くなってしましました。具体的にはBeep用のデバイスが壊れたようです。購入時にチェックをしたつもりですが今回久...