LoginSignup
11
5

More than 1 year has passed since last update.

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

Last updated at Posted at 2022-01-24

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( {});
これがそう ラムダ式:簡易的な関数オブジェクトをその場で定義するための機能 だった💦
この関数内で定義した変数はなにか妙な動きをするようで...これに数日悩まされました

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.requestFromWire.read を行わせる事で回避しました

9. 使い方

IMG_4184.jpg

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さんを参考にさせて頂きました
いつもありがとうございます
それにしても Library として配布するのはとても緊張しますね
Lovyan03さんとかの精神はかなり強いのだなぁと思います
今後もよろしくお願い申し上げます

11
5
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
11
5