#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. 使い方
library化してありますので
こういうプログラムがあったら
//
// 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サーバを立ててセンサを繋ぎ
#include "WiFiWire.h"
#include "AsyncUDP.h"
WiFiWire WWire(WIFIWIRE_SERVER);
void setup()
{
Serial.begin( 115200 );
while (!Serial);
WWire.begin();
}
void loop()
{
WWire.update();
}
クライアント側から同じようにアクセスできます
#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さんとかの精神はかなり強いのだなぁと思います
今後もよろしくお願い申し上げます