気の向くままに辿るIT/ICT/IoT
IoT・電子工作

PN532とWT32-ETH01でスマートロック認証

ホーム前へ次へ
ESP8266って?

PN532とWT32-ETH01でスマートロック認証

PN532とWT32-ETH01でスマートロック認証

2024/10/25

 運用中の自作スマートロックにおけるカード等認証ボードPN532/NFC MODULE V3を無線なESP32ではなく、買ってみれば技適が微妙ながら無線は使わないし...と、有線なWT32-ETH01で実装してみた話。

 というのも今年2月から数ヶ月安定運用できていたカード&パスワード認証でしたが、暗証番号認証で継続使用は可能もタイミング的には酷暑を境に比較的涼しくなってからもWifiMultiを使ってすら、中継器を導入、電波(Wi-Fi)強度を上げてすら、もっても3日などPN532ボードを見失い、カード認証不能になることが頻発するようになった原因が、無線にあるのか否かを判別するため。

概要

 冒頭リンクの通り、WT32-ETH01ボードに予め入っていたプログラムによる実行結果表示はFTDIモジュールとの併用でできたものの、サンプルスケッチETH_LAN8720のアップロードができず。

 そこでFTDIモジュールではなく、アップローダとしてESP32開発ボードを介してみるとアップロードできるようにはなりました。

 が、サンプルスケッチの結果を得ることができず、他のライブラリをとWebServer_WT32_ETH01ライブラリのスケッチHelloServer2で試すと相応の結果を得ることに成功。

 ここで自作のスマートロックにおける認証ロジックを組み込んでみると、すんなりとはいかず、いくつかハマりどころがあったものの、最終的には実装することができるようになりました。

 尚、未確認ではありますが、もしかするとArduino IDEに標準で入っていて?当初結果を確認できなかったETH_LAN8720も今となっては何らかの結果を得られるのではと思っています。

Wireless TagチップとESP-WROOM-32チップ

2024/11/21

 WT32-ETH01ボードには、ESP-WROOM-32チップが載ったものとWireless Tagチップが載ったものがあります(他にもあるのかは不明)。

 ハマりどころの原因は、前者に続いて後者を試したから、その違いがあったというべきか、Wireless Tagならではというべきか。

 1度めにESP-WROOM-32チップのボード、2度め、3度めにWireless Tagチップのボードを計3つ買ってみて、そう思うに至りました。

ハマりどころ

 ハマった感が大きい順は、こんな感じ。

  1. startNFC(){}ロジックの実行位置
  2. ETH.hの参照パス
  3. ETH.begin()の引数順
  4. タスク関数ループ内でのvTaskDelay(10);

 最終的に最も大きなハマりどころは、startNFCロジックの配置でした。

 それも途中は、何の問題もなく、何度やっても["Found chip PN532"]となり、いけていたのに、なぜか、完成間近になって突然、["Didn't find PN53x board"]にしかならなくなって、結果、nfc.begin以下、NFC初期化ロジック自体をタスク関数内に実装する必要があるのか?と気づき、修正、完成したという謎の経緯が。

 ESP32ボードでは、その必要性は全くなかった(setup(){}内で実行すればよかった)のでWT32-ETH01ボードでこうする必要があったのはホントに謎。

 続いて気づくのに時間が最もかかってしまい、泡を食ったのが、2のETH.hの参照先。

 ESP32コアにもあったらしく、コンパイル時、(Linuxホストでは、)[~/.arduino15/packages/esp32/hardware/esp32/3.0.5/libraries/Ethernet/src/]以下のファイルを使っており、WebServer_WT32_ETH01ライブラリのパスとは違い、かつ、ETH.h、ETH.cppの当該定義も異なっていたことから、3のETH.begin()の引数順も微妙に違ったことでした。

 結果、WebServer_WT32_ETH01ライブラリのサンプルにあった引数2つの有効行、引数の数は6個あったコメントアウト行何れを有効にして実行してみたところで、何れもコンパイルエラー...。

 これが厄介だったのは、前述したように途中、サンプルスケッチにあるコメントアウトされた選択肢で事足りており、引数6個の方ならいけるはずという思い込みと、何かの折に第1引数を*TYPEとして通ったという誤った成功体験から、そうしたところ、たまたま、6個全ての引数の型が一致してしまい、コンパイルが通ってしまったに過ぎなかったこと。

 当然、実行してみたところで固定としたIPアドレスも設定されず、先に進まない事態になり、勝手に原因をわかりにくくしてしまったというオチ。

 尤もArduino IDEのスケッチ書き込み時のエラーなどに目を凝らしてちゃんと読んでいれば、避けられたはず(思い込みから読み飛ばしたり、勘違いし続けていた可能性もありますが)。

 更にタスク関数ループ内でのvTaskDelay(10)、ESP32開発ボードで実装しているものは、この遅延設定は不要だったのでWT32-ETH01で必要となったのは、やや意外で、もしやと思うまでに時間を要しました。

 尚、これは、「タスクがリセットされなかった」旨のエラーとエンドレスな再起動を解消するのに必要でした。

2024/11/02

 うっかり、WT32-ETH01ボードをショートさせ、完全に通電しなくなってしまい、改めて購入したものが届いたので動作確認してみたところ、次のようにハマりました。

  1. WT32-ETH01ボードとして認識されない

 なんとスケッチをアップしようと思ったらWT32-ETH01ボードとして認識されないというもの(これは、何かの際にMACアドレスが見当たらないと言われたのと関係がある?)。

 ショートさせた無駄に技適を偽装したっぽいボードでは、こうしたことはなかったのですが、もう1個は試していないものの、新たに買ったWireless Tagの正規品っぽい方で遭遇。

 エラーを見るとWebServer_WT32_ETH01.hで#if ESP32に入らず、#elseのエラーとなっていました。

 そこで[Arduino/libraries/WebServer_WT32_ETH01/src/WebServer_WT32_ETH01.h]の#if #else #endif文をコメントアウト、[#define BOARD_NAME  <WT32-ETH01>]行を強制的に定義することで回避、スケッチもアップすることができるようになりました。

  1. [ETH Started]でプログラムが止まる

 何をしてもシリアルモニタ上でスケッチに書いた覚えのない(ETH.hに書かれた)[ETH Started]で止まるので何かと思ったら、イーサネットケーブルを挿し忘れていました...。

 よって、この場合、イーサネットケーブルを接続しましょう...。

回路 2024/11/09追記

自作スマートロックRFID認証用WT32ETH01+PN532+Bi color LED module回路 by Fritzing

 PoEハブからCAT5e以上のLANケーブル1本でイーサネットプラグとDCプラグ(DC12V 2A)から成るPoEスプリッタのイーサネットケーブルで通信、降圧モジュールでDC12Vから5V電源をとり、WT32-ETH01ボードへの給電と通信をまかない、WT32-ETH01の3.3V/GND/485_EN/CFGでPN532モジュールのVCC/GND/SDA/SCLと任意の出力用GPIOピン2本及びGNDで2カラーLEDモジュールのS/無印/-を接続した回路は、こんな感じ。

スケッチ

 スケッチは、WebServer_WT32_ETH01ライブラリのスケッチWebClientをベースに自作スマートロックのRFIDクライアント用スケッチを組み込みました。

 尚、WebClientの内、LAN環境に合わせmyIP/myGW/myDNS値を変更、char server[] = "arduino.cc";行を削除、WiFiClient!?オブジェクト名を変更、setup(){}内のwhile (!Serial && (millis() < 3000));行及び当該コメント行、サーバ接続時への通信、printoutData()関数、printoutData()行含む、loop(){}内の処理を削除。

// スマートドアロックシステム/RFID認証
// WT32-ETH01 TCPクライアント
// RFID入力/転送 + OK/NG LED
 
#define DEBUG_ETHERNET_WEBSERVER_PORT Serial
 
// Debug Level from 0 to 4
#define _ETHERNET_WEBSERVER_LOGLEVEL_    3
 
#include <WebServer_WT32_ETH01.h>
 
#include <Arduino.h>
#include <ArduinoOTA.h>
#include <ESPmDNS.h>
 
#include <Wire.h>
#include <PN532_I2C.h>
#include <PN532.h>
 
PN532_I2C pn532i2c(Wire);
PN532 nfc(pn532i2c);
 
#include <PN532_debug.h>
 
#define RED_LED 14
#define GREEN_LED 12
 
const String IMYME = "DoorKeyer";
const String MES_ALLOW = "ALLOW";
const String MES_DENY = "DENY";
const String MES_RECEIVED = "RECEIVED";
const String NUM_RESET = "0000";
 
const char* serverAddress = "192.168.1.251";
const int serverPort = 12345;
 
const char common_name[40] = "wt32_door_rfider";
const char* OTAName = common_name;
const char* mdnsName = common_name;
 
IPAddress ip(192, 168, 1, 249);
IPAddress gateway(192, 168, 1, 1);
IPAddress subnet(255, 255, 255, 0);
 
WiFiClient TCPkeyer;
 
String idcard;
 
TaskHandle_t RfidTask;
 
void setup() {
  Serial.begin(9600);
  while (!Serial);
 
  pinMode(GREEN_LED, OUTPUT);
  pinMode(RED_LED, OUTPUT);
 
  xTaskCreatePinnedToCore(
    Task4Rfid,   /* タスク関数 */
    "RFIDTask",  /* タスク名 */
    8192,        /* タスクのスタックサイズ */
    NULL,        /* タスクのパラメータ */
    1,           /* タスクの優先順 */
    &RFIDTask,   /* 生成したタスクのトラックを維持するためのタスクハンドル */
    0);          /* core 0へのピンタスク */
  delay(500);
 
  Serial.print("\nStarting WebClient on " + String(ARDUINO_BOARD));
  Serial.println(" with " + String(SHIELD_TYPE));
  Serial.println(WEBSERVER_WT32_ETH01_VERSION);
 
  // To be called before ETH.begin()
  WT32_ETH01_onEvent();
 
  ETH.begin(ETH_PHY_TYPE, ETH_PHY_ADDR, ETH_PHY_MDC, ETH_PHY_MDIO, ETH_PHY_POWER, ETH_CLK_MODE);
 
  ETH.config(myIP, myGW, mySN, myDNS);
 
  WT32_ETH01_waitForConnect();
 
  Serial.println();
  Serial.println(F("Starting connection to server..."));
  Serial.print(F("ETH.localIP() : "));
  Serial.println(ETH.localIP());
 
  startOTA();
  startMDNS();
 
  if (TCPkeyer.connect(serverAddress, serverPort)) {
    Serial.println("Connected to TCP server on setup()");
  } else {
    Serial.println("Failed to connect to TCP server on setup()");
    if (TCPkeyer.connect(serverAddress, serverPort)) {
      Serial.println("Reconnected to TCP server on setup()");
    } else {
      Serial.println("Failed to Reconnect to TCP server on setup()");
    }
  }
}
 
void loop() {
  ArduinoOTA.handle();
  delay(1);
}
 
void Task4Rfid( void * pvParameters ) {
  Serial.print("RFIDTask running on core ");
  Serial.println(xPortGetCoreID());
 
#include <PN532_I2C.h>
#include <PN532.h>
 
  PN532_I2C pn532i2c(Wire);
  PN532 nfc(pn532i2c);
 
  startNFC();
 
  for (;;) {
    read_card();
    vTaskDelay(10);
  }
  delay(1);
  vTaskDelete(NULL);
}
 
void read_card() {
  bool success ;
  uint8_t uid[] = { 0, 0, 0, 0, 0, 0, 0 };  // Buffer to store the returned UID
  uint8_t uidLength;
 
  success = nfc.readPassiveTargetID(PN532_MIFARE_ISO14443A, uid, &uidLength, 10);
  if (success) {
    idcard = "";
    for (byte i = 0; i <= uidLength - 1; i++) {
      idcard += (uid[i] < 0x10 ? "0" : "") +
                String(uid[i], HEX);
    }
    if (!TCPkeyer.connected()) {
      Serial.println("Connection is disconnected");
      TCPkeyer.stop();
 
      if (TCPkeyer.connect(serverAddress, serverPort)) {
        Serial.println("Reconnected to TCP server");
        startNFC();
      } else {
        Serial.println("Failed to reconnect to TCP server");
        if (TCPkeyer.connect(serverAddress, serverPort)) {
          Serial.println("2 Try Reconnected to TCP server");
          startNFC();
        } else {
          Serial.println("2 Failed to reconnect to TCP server");
        }
      }
    }
    delay(100);
    if (TCPkeyer.connect(serverAddress, serverPort)) {
      TCPkeyer.println(IMYME + ": key n" + idcard + "\r");
      delay(1000);
      //Serial.println("idcard " + idcard);
      String answer = TCPkeyer.readStringUntil('\r');
      //Serial.println("from " + answer);
      if (answer.indexOf("x") >= 0) {
        String res = answer.substring(answer.indexOf("x") + 1, answer.length());
        Serial.print("response from DoorLocker : ");
        Serial.println(res);
        if (res == MES_ALLOW) {
          Serial.println("GREEN LED ON");
          digitalWrite(GREEN_LED, HIGH);
          digitalWrite(RED_LED, LOW);
          delay(1000);
          digitalWrite(GREEN_LED, LOW);
          digitalWrite(RED_LED, LOW);
        } else if (res == MES_DENY) {
          Serial.println("RED LED ON");
          digitalWrite(RED_LED, HIGH);
          digitalWrite(GREEN_LED, LOW);
          delay(1000);
          digitalWrite(RED_LED, LOW);
          digitalWrite(GREEN_LED, LOW);
        }
      }
    } else {
        Serial.println("Connection is disconnected on else state");
        TCPkeyer.stop();
        delay(100);
 
        if (TCPkeyer.connect(serverAddress, serverPort)) {
            Serial.println("Reconnected to TCP server 2 on read_card()");
            startNFC();
        } else {
            Serial.println("Failed to reconnect to TCP server 2 on read_card()");
            if (TCPkeyer.connect(serverAddress, serverPort)) {
              Serial.println("2 Try Reconnected to TCP server 2 on read_card()");
              startNFC();
            } else {
              Serial.println("2 Failed to reconnect to TCP server 2 on read_card()");
            }
        }
    }
  }
  delay(10);
}
 
void reset(String keyval) {
  if (!TCPkeyer.connected()) {
    Serial.println("Connection is disconnected on reset()");
    TCPkeyer.stop();
    delay(100);
 
    if (TCPkeyer.connect(serverAddress, serverPort)) {
      Serial.println("Reconnected to TCP server on reset()");
      startNFC();
    } else {
      Serial.println("Failed to reconnect to TCP server on reset()");
      if (TCPkeyer.connect(serverAddress, serverPort)) {
      Serial.println("Failed to reconnect to TCP server");
        Serial.println("2 Try Reconnect to TCP server on reset()");
        startNFC();
      } else {
        Serial.println("2 Failed to Reconnect to TCP server on reset()");
      }
    }
  }
  delay(100);
  if (TCPkeyer.connect(serverAddress, serverPort)) {
    Serial.println("send keyval to server");
    TCPkeyer.println(IMYME + ": key n" + keyval + "\r");
    delay(1000);
    String answer = TCPkeyer.readStringUntil('\r');
    Serial.println("from " + answer);
    if (answer.indexOf("x") >= 0) {
      String res = answer.substring(answer.indexOf("x") + 1, answer.length());
      Serial.print("response: ");
      Serial.println(res);
      if (res == MES_RECEIVED) {
        Serial.println("GREEN LED ON");
        digitalWrite(GREEN_LED, HIGH);
        digitalWrite(RED_LED, LOW);
        delay(500);
        digitalWrite(GREEN_LED, LOW);
        digitalWrite(RED_LED, LOW);
        delay(500);
        digitalWrite(GREEN_LED, HIGH);
        digitalWrite(RED_LED, LOW);
        delay(500);
        digitalWrite(GREEN_LED, LOW);
        digitalWrite(RED_LED, LOW);
        delay(500);
        digitalWrite(GREEN_LED, HIGH);
        digitalWrite(RED_LED, LOW);
        delay(500);
        digitalWrite(GREEN_LED, LOW);
        digitalWrite(RED_LED, LOW);
        //delay(100);
        //ESP.restart();
      }
    }
  }
}
 
void startNFC() {
  Serial.println("ESP32: TCP CLIENT + send ID & turn on Color LED according to ret");
 
  delay(1000);
  nfc.begin();
 
  delay(500);
 
  uint32_t versiondata = nfc.getFirmwareVersion();
  if (!versiondata) {
    Serial.print("Didn't find PN53x board");
    //while (1); // halt
    while (1) {
      delay(10);
    };
  } else {
    Serial.print("Found chip PN5"); Serial.println((versiondata >> 24) & 0xFF, HEX);
    Serial.print("Firmware ver. "); Serial.print((versiondata >> 16) & 0xFF, DEC);
    Serial.print('.'); Serial.println((versiondata >> 8) & 0xFF, DEC);
 
    // Set the max number of retry attempts to read from a card
    // This prevents us from waiting forever for a card, which is
    // the default behaviour of the PN532.
    nfc.setPassiveActivationRetries(0xFF);
 
    nfc.SAMConfig();
  }
}
 
void startMDNS() {
  MDNS.begin(mdnsName);
  Serial.print("mDNS responder started: http://");
  Serial.print(mdnsName);
  Serial.println(".local");
}
 
void startOTA() {
  ArduinoOTA.setHostname(OTAName);
  //  ArduinoOTA.setPassword(OTAPassword);
 
  ArduinoOTA.onStart([]() {
    Serial.println("Start");
    // turn off the LEDs
    for (int i = 0; i < 6; i++) {
      //      digitalWrite(LED_BUILTIN, HIGH);
      //      digitalWrite(LED_BUILTIN, LOW);
    }
  });
  ArduinoOTA.onEnd([]() {
    Serial.println("\r\nEnd");
  });
  ArduinoOTA.onProgress([](unsigned int progress, unsigned int total) {
    Serial.printf("Progress: %u%%\r", (progress / (total / 100)));
  });
  ArduinoOTA.onError([](ota_error_t error) {
    Serial.printf("Error[%u]: ", error);
    if (error == OTA_AUTH_ERROR) Serial.println("Auth Failed");
    else if (error == OTA_BEGIN_ERROR) Serial.println("Begin Failed");
    else if (error == OTA_CONNECT_ERROR) Serial.println("Connect Failed");
    else if (error == OTA_RECEIVE_ERROR) Serial.println("Receive Failed");
    else if (error == OTA_END_ERROR) Serial.println("End Failed");
  });
  ArduinoOTA.begin();
  Serial.println("OTA ready\r\n");
}

 そんなWT32-ETH01+PN532によるRFID認証用のスケッチはこんな感じ。

 そう言えば、WT32-ETH01は、FTDIやESP開発ボードをアップローダとしてアップロードできる一方、一度設定すれば、OTA(無線)アップデートできると謳う製品でした。

 うっかり、ArduinoOTA(とESPmDNS)を使ってしまいましたが、正規の方法があるはずですよね?

 ArduinoOTAでもLANケーブルと電源をつないで、GPIO0とGNDをジャンパさえすれば、OTAアップデートもできました。

(ちなみにESP32開発ボードとWT32-ETH01ボードをTX/RX含め、アップローダとして接続したままでもOTAアップデートでいけましたが。)

 OTAアップロード後に実行する際には、GPIO0とGNDのジャンパを外し、電源を再投入する必要はありましたが。

 尚、少なくとも数回やってみた限りにおいて、この電源再投入後は、PN532ボードは100%認識されました。

 ただ、仮に1回で済むにしても、せっかく無線なのにジャンパしたり、外したりって煩わしいわけですが、正規のOTAアップデートできる方法なら、ソフトウェア的に代替できたりしてGPIO0とGNDのジャンパも要らないのかな?

 気が向いたら試してみよう...。

2024/11/03

 相変わらず、GPIO2とGNDのジャンパではOTAアップデートできません、というか、できるとしても、やり方がわかりません...。

 よってArduinoOTAでシリアルポートにOTAポートを指定、かつ、アップデートする際は、WT32-ETH01ボードの2ではなく、GPIO0とGNDをジャンパしておき(、終わったらジャンパを外し、電源再投入)という運用とすることにしました。

 OTAアップデートなのにジャンパしたり、外したりしなくてはならないのは、かなり微妙ですが。

 それとは別にキーパッド認証は元々のESP32、PN532 RFID認証にはWT32-ETH01と分けた関係でLEDも分けるべく、後者用に手持ちのBi(Two)カラーLEDモジュールを使用することに。

 結果、緑、赤、両方、OFFできるArduino用回路とプログラムを使わせていただきました(なぜか、背景に蜘蛛や虫が這い回るという独特なデザインのサイトなので苦手な方は見ない方が...)。

 ちなみにBi color LEDは、PWMでKY-029 Dual Color LED Moduleのようにも使える模様。

 WT32-ETH01のピン出力が3.3Vだからでしょう、手持ちは電子工作を始めた頃に買った37センサーキットにあったもので大小2つあったのですが、何れもボード上のSMD LEDは赤く光るものの、色が変わる砲弾型LEDが光るのは小さい方のみだったのでこれを使用。

 モジュールは、S、無印と-(マイナス)の3ピン、無印はS(ignal)とは別に電源としての+だとばかり思い込んでいましたが、このモジュールでは?Sと無印は、緑と赤のLEDに該当する模様でそれぞれ抵抗を噛ませつつ、LED用にピンを2つ設定、緑の時は緑をHIGH、赤をLOW、赤の時は、この逆、OFFの時は、両方ともLOWとすることで、うまく動きました。

WiFiライブラリ使用もWi-Fi通信は必要なさ気

 WebServer_WT32_ETH01.hccでWiFi.hが読み込まれていますが、無線・有線で共用しているのかライブラリとして参照しているのみで内蔵Wi-Fiによる通信は行われていない模様です。

 サンプルスケッチを見てもアクセスポイント(ssid/pathphrase)設定があるわけでもなく、setup(){}でそもそもWIFI_STA/WIFI_AP等々の指定もなく(もちろんオリジナルスケッチで指定する必要もなく)、念の為、あえてsetup(){}内冒頭でWiFi.disconnect(true);と設定してアップし、実行してみましたが、スケッチ上はWiFiClientオブジェクトが宣言されつつ、これらでもTCP通信を始め機能に全く支障はないので。

 よってWiFiClientとなっているのは、ライブラリを参照しているからに過ぎず、WT32-ETH01においては、イーサネットクライアントとして使われていると考えて良さそうです(と言っても開発者に聞いたわけではないですし、公式ドキュメントでの言及も見当たらない中、私はそう信じているわけですが、これいかに)。

備考

 ちなみに運用中に何らかの理由でWT32_ETH01ボードの電源を落とし、Arduino IDEを起動した後にスケッチをアップロードする目的などでGPIO0とGNDをジャンパしたまま、WT32_ETH01ボードを起動すると起動後にジャンパを外したとしてもArduino IDEではOTAポートが認識されないので注意。

 ただ、その際、GPIO0とGNDのジャンパを外した状態で電源を入れ直すか、もしくは、Arduino IDEを再起動することでOTAポートが認識されるようになります。

 尚、前者は、認識されるまで、やや時間がかかる場合があり、後者は、IDE起動直後には認識されているはずです(が、IDEの起動に相応に時間がかかるので、あまり変わらないかもしれませんが)。

 OTA、OTAと言ってますが、今回、WT32-ETH01ボードでは無線は使わず、有線のみなのでOn The Air(無線)じゃないですが。

ホーム前へ次へ