【WiFiWire™】i2cのprotocolをWiFiに乗せたら凄いのが爆誕した話

#1. はじめに
明けましておめでとうございます
本年最初の投稿となります
年末から年始にかけまして Twitter垢が凍結されたり
まさかと思っていた 某所のセンサが誤作動を起こしたり
例のウィルスで全然県外移動が制限されたり
色々あるよねって感じです(涙

#2. これが生まれた背景
某所のセンサが急に移動にされ
細いケーブルを取りまわしたり 100本圧着PINなんて外でやるものではなく
話を聞くとまた移動する可能性があったりと
まぁ　"落ち着かない"方々が多い現場では よくありがちな背景
私の扱っているセンサはほぼI2Cで構成されているので
WiFiの受信範囲であれば大体OKとかいう単純な考えですます

#3. 考案前のお話
過去に __無センサ__(無線+センサ) とか言う造語を作って
一斉を風靡した事も記憶に新しい話ではございますが
下記の図のようにServer側にLibraryが必要であり
ちょっとイケてね～って事に年末気が付きました

* ここで言う Server と Client はあくまでも待つ側 操作する側の意味であり UDPの方式に特化した名称であることにご留意下さい

```
Past works

UDPServer                          UDPClient
+----------+                       +----------+
| CPU      |                       | CPU      |
+----------+       AsyncUDP        +----------+                       
| OS       | <...................> | OS       |
+----------+       WiFiWire™       +----------+
| Sensor   |                        
| Library  |
+-+-+-+-+--+
  | | | | I2C Sensors
  | | | |
  G V D C

```
今回のはClient 側にLibraryを持たせるという
なんだかとんでも無いものを爆誕した訳です

```
This work

UDPServer                          UDPClient
+----------+                       +----------+
| CPU      |                       | CPU      |
+----------+       AsyncUDP        +----------+                       
| OS       | <...................> | OS       |
+-+-+-+-+--+       WiFiWire™       +----------+
  | | | |                          | Sensor   |
  | | | |                          | Library  |
  | | | |                          +----------+
  G V D C
                                                                                                    
```

#4. ここでワンポイント UDPのお話
ESP32ではWiFiUDPとAsyncUDPの2種類の方式があり
WiFiUDPの方がきめ細かい制御が出来そう...
ですが受信するタイミングを管理しないといけないようで
今回はAsyncUDPを採用しました
これがラムダ式に嵌る事になるきっかけになるとわ...

```
Received part excerpt (Server side)
    
if (udp.listen(1234)) {
  Serial.print("UDP Listening on IP: ");
  Serial.println(WiFi.localIP());
  udp.onPacket([](AsyncUDPPacket packet) {
    Serial.print("UDP Packet Type: ");
    Serial.print(packet.isBroadcast()?"Broadcast":packet.isMulticast()?"Multicast":"Unicast");
    Serial.print(", From: ");
    Serial.print(packet.remoteIP());
    Serial.print(":");
    Serial.print(packet.remotePort());
    Serial.print(", To: ");
    Serial.print(packet.localIP());
    Serial.print(":");
    Serial.print(packet.localPort());
    Serial.print(", Length: ");
    Serial.print(packet.length());
    Serial.print(", Data: ");
    Serial.write(packet.data(), packet.length());
    Serial.println();
    //reply to the client
    packet.printf("Got %u bytes of data", packet.length());
   });
}

```
上記の udp.listen(1234) は
『今繋がっているUDPのIPアドレスの1234ポートからのデータを待ちますよ』っていう意味であり
実際にデータを受信するのは udp.onPacket([](AsyncUDPPacket packet) {});
これがそう ___ラムダ式:簡易的な関数オブジェクトをその場で定義するための機能___  だった💦
この関数内で定義した変数はなにか妙な動きをするようで...これに数日悩まされました


#5. ラムダ式の対策は？
見る事が出来ていて それに対するreplyも送信出来ているわけですから
このラムダ式から外に変数の内容をpushしておけば変数の値は保持できると気が付き
関数内でpush 関数外でpopするような仕組みを実装

#6. 通信の方法は?
udp.onPacket で受けたデータに対するreplyは上記のソースにもあるように
packet.print / paket.write 等で送信することが出来るようで
ServerからClientに対しての送信は問題がない

```
Received part excerpt (Client side)

if (udp.connect(IPAddress(192.168.4.1), 1234) {
  Serial.print("UDP Listening on IP: ");
  Serial.println(WiFi.localIP());
  udp.onPacket([](AsyncUDPPacket packet) {
    Serial.print("UDP Packet Type: ");
    Serial.print(packet.isBroadcast()?"Broadcast":packet.isMulticast()?"Multicast":"Unicast");
    Serial.print(", From: ");
    Serial.print(packet.remoteIP());
    Serial.print(":");
    Serial.print(packet.remotePort());
    Serial.print(", To: ");
    Serial.print(packet.localIP());
    Serial.print(":");
    Serial.print(packet.localPort());
    Serial.print(", Length: ");
    Serial.print(packet.length());
    Serial.print(", Data: ");
    Serial.write(packet.data(), packet.length());
    Serial.println();
    //reply to the client
    packet.printf("Got %u bytes of data", packet.length());
   });
}

```
Client側もudp.onPacket で受けたデータに対するreplyは上記のソースにもあるように
packet.print / paket.write 等で送信することが出来るようで
ServerからClientに対しての送信は問題がない
問題は Client 側からcmd をどう送れば良いのか...

```
Excerpt from the transmitter on the Client side

 udp.write(uint8_t *send, uint16_t sendCnt);

```
udp.connectで すでに接続されている相手に対しては
簡単だった💦

#7. ハンドシェイク
今回 WiFiWire™　として実装するのは以下の7個
今後の使い勝手を考えて library化を行う事にした(初)

```

Wire.begin(void);
Wire.beginTransmission(int);
Wire.requestFrom(int, int);
Wire.endTransmission(bool);
Wire.available(void);
Wire.read(void);
Wire.write(uint8_t);

```

各々の関数(メソッド)は色々処理を行うのですが
肝心な UDP接続 と Async受信/送信がキモになったようです
例えば一番簡単な Wire.write()

```
//===========================================
// write
//-------------------------------------------
// Client -> Server
//===========================================
int16_t WiFiWire::write(uint8_t dt)
{
  uint16_t rtn = 0;
  _sendCnt = 0;
  _send[_sendCnt] = 'w'; _sendCnt ++;
  sendUDP();
  return rtn;
}

:
:
:

udp.onPacket([](AsyncUDPPacket packet) {
  uint8_t *recv = packet.data();
  uint8_t cmd = recv[0];
  switch(cmd) {
    //===========================================
    // write
    //-------------------------------------------
    // Server -> Wire
    //===========================================
      case 'w':
        Wire.write(recv[1]);
        break;
  }

});

```
AsyncUDPの特性を生かし(?)
CLient側から Wire.write を引数付きで発行すると 先頭に 'w' 続いて '引数'というパケットを作成送信
Server側は onPaket で内容を受け case 'w' の時に限り Wire.write(引数) を実行するという
__行って来い__ 的な内容です💦

#8. 難関の Wire.read
これは最初一番簡単に考えていたのですが
実は一番面倒な処理で...

Client側が　__Wire.read__ を発行するタイミングではまだ
Server側が __Wire.read__ して居なかったようで
これは __Wire.requestFrom__ に __Wire.read__ を行わせる事で回避しました
#9. 使い方
![IMG_4184.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2063214/a58bdb92-28f0-71b1-ddce-a9d6bd9939fb.jpeg)

library化してありますので
こういうプログラムがあったら

``` ADT7410.ino
//
// ADT7410.ino
// THX. by https://hatakekara.com/adt7410/
//

#include <Wire.h>
#define MYSDA (25)
#define MYSCL (21)
#define SENS_ADRS (0x49)
#define TEMP_REGS (   0)
#define RECV_BYTE (   2)

void setup()
{
  Serial.begin( 115200 );
  while (!Serial);
  Wire.begin(MYSDA, MYSCL, 400);

  Wire.beginTransmission(SENS_ADRS);
  Wire.write(TEMP_REGS);
  Wire.endTransmission(false);
  Wire.requestFrom(SENS_ADRS, RECV_BYTE);
  while(1) {
    if (Wire.available() >= RECV_BYTE) break;
  }

  uint16_t dt = 0x0000;
  float tmp = 0.0;
  dt  = Wire.read() << 8;
  dt |= Wire.read();

  Serial.printf("dt = 0x%04x\n", dt);
  Serial.printf("Temp = %.2f'C\n",(float)(dt >> 3)/16.0);
}

void loop()
{
}

```

こういう AsyncUDPサーバを立ててセンサを繋ぎ

``` Server.ino
#include "WiFiWire.h"
#include "AsyncUDP.h"

WiFiWire WWire(WIFIWIRE_SERVER);

void setup()
{
  Serial.begin( 115200 );
  while (!Serial);
  WWire.begin();
}

void loop()
{
  WWire.update();
}

```
クライアント側から同じようにアクセスできます

``` Client.ino
#include "WiFiWire.h"
#include "AsyncUDP.h"

WiFiWire WWire(WIFIWIRE_CLIENT);

#define SENS_ADRS (0x49)
#define TEMP_REGS (   0)
#define RECV_BYTE (   2)

float tempRead(void)
{
  float rtn = 0.0;
  
  WWire.beginTransmission(SENS_ADRS);
  WWire.write(TEMP_REGS);
  WWire.endTransmission(false);
  WWire.requestFrom(SENS_ADRS, RECV_BYTE);
  while(1) {
    if (WWire.available() >= RECV_BYTE) break;
  }

  uint16_t dt = 0x0000;
  dt  = WWire.read() << 8;
  dt |= WWire.read();

  Serial.printf("dt = 0x%04x\n", dt);
  rtn = (float)(dt >> 3) / 16.0;
  return rtn;
}

void setup(void)
{
  Serial.begin( 115200 );
  while (!Serial);
  WWire.begin();

  Serial.printf("%.2f'C\n", tempRead());
}

void loop(void)
{
  WWire.update();
}

```


#10. 全ソース
今回の期に github もデビュ致しました(初)
https://github.com/chrmlinux/WiFiWire


#11. 今後の開発内容
* I2C側の Wire.onReceive() と Wire.onReceive() が実装されていないので そのあたりをなんとなく
* WiFiSPI™ WiFi1Wire™ WiFiUART™ っぽいのも良いのかなぁ
* crc やら エラー対策

#12. 最後に
asyncUDP 等の詳細に関しましては
[lang-shipさん](https://lang-ship.com/blog/work/esp32-asyncudp/)を参考にさせて頂きました
いつもありがとうございます
それにしても Library として配布するのはとても緊張しますね
[Lovyan03さん](https://twitter.com/lovyan03)とかの精神はかなり強いのだなぁと思います
今後もよろしくお願い申し上げます