アクセスポイント側から見ると、新たな接続要求があったクライアントが、ブラウザなのかセンサデバイスなのかをその時点で識別することはできない。もし、全てのクライアントにウェッブページの情報を送信してしまうと、センサデバイスにとって無価値な情報が送られてきて、負荷が増大することになる。
構築するネットワークでは、ブラウザからのパラメータ変更要求などを含めて、以下のように取り決めた。
- ブラウザはアクセスポイント接続後、テキストとして「Web」を送信する。
- センサデバイスはアクセスポイント接続後、テキストとして「Dev」を送信する。
- センサデバイスから送信するセンサ情報(実際には、先頭1byteは0から99までの循環カウンター値)が、複数バイトの場合は終端(末尾)を0xFFとする。
- ブラウザからのインターバル変更要求は、テキスト「I」に続いて、ミリ秒単位のバイナリデータを付与して送信する。
- ブラウザからのデータサイズ変更要求は、テキスト「S」に続いて、1024byte単位のバイナリデータを付与して送信する。ただし、バイナリが0の場合は、例外としてデータサイズを1byteとする。
以下に、WebSocketイベントのコールバック関数を示す。この関数の引数は、「内部的に付与されるクライアントの番号(0から昇順)」、「イベントのタイプ(接続、切断、テキスト情報など)」、「受信データ配列ポインタ」、「受信データサイズ」である。プログラムについての詳細は省くが、次の2点に注意が必要である。
- ブラウザからパラメータ(インターバル、データサイズ)の変更要求があった場合、変更フラグの設定だけを行う。この関数内で接続クライアントへの変更命令送信を行うと、次に発生するイベントの遅延が生じる可能性があるためである。
- 新規に接続したクライアントへの上記パラメータの送信は、接続直後に行うとクライアントの取りこぼしが生じる場合がある。そのため、ハンドシェイク確立後、すなわちPONGイベント発生後にこれを行う。
void webSocketEvent(uint8_t num, WStype_t type, uint8_t * payload, size_t length) {
IPAddress ip = webSocket.remoteIP(num);
switch(type) {
case WStype_DISCONNECTED: {
clientSTAT.Connect[num] = 0;
Serial.printf("[%u] Disconnected\n", num);
}
break;
case WStype_CONNECTED: {
clientSTAT.Address[num] = ip[3]; // IPアドレス末尾
clientSTAT.Station[num] = 0; // ブラウザ or デバイス未確認
clientSTAT.Connect[num] = 1; // 接続
clientSTAT.Elapsed[num] = 0; // 連続接続時間クリア
clientSTAT.TimeOut[num] = 0; // タイムアウトカウンター初期化
Serial.printf("[%u] Connected from %d.%d.%d.%d\n", num, ip[0], ip[1], ip[2], ip[3]);
}
break;
case WStype_TEXT: {
Serial.printf("[%u] get Text: %s\n", num, payload);
if (payload[0] == 'W') clientSTAT.Station[num] = 1; // "Web" ウェブブラウザ設定
else if (payload[0] == 'D') clientSTAT.Station[num] = 2; // "Dev" デバイス設定
else if (payload[0] == 'I') { // インターバル変更要求(msec単位 ⇒ 100msec倍数に変換)
uint32_t interval = (uint32_t)strtol((const char *) &payload[1], NULL, 10); // 10進変換
interval = interval / 100;
// インターバル100msec以上の変更
if (interval >= 1 && intervalBase != (int)interval) {
intervalBase = (int)interval;
intervalFlag = true; // インターバル変更告知要求
}
}
else if (payload[0] == 'S') { // データサイズ変更要求
uint32_t datasize = (uint32_t)strtol((const char *) &payload[1], NULL, 10); // 10進変換
if (datasizeBase != (uint8_t)datasize) {
datasizeBase = (uint8_t)datasize;
datasizeFlag = true; // データサイズ変更告知要求
}
}
}
break;
case WStype_BIN: {
// インターバル間隔でのクライアントからのバイナリデータ受信
if (length <= WEBSOCKETS_MAX_DATA_SIZE) {
// 通信速度測定バッファへ保存
digitalWrite(STATPin, HIGH);
for (uint16_t i = 0; i < length; i++) buffer[i] = payload[i];
digitalWrite(STATPin, LOW);
clientSTAT.Station[num] = 2; // デバイス接続
clientSTAT.Connect[num] = 2; // データ受信
if (length != 1 && buffer[length - 1] != 0xFF) buffer[0] = 255; // エラー発生(データ255送信)
clientSTAT.GetData[num] = buffer[0]; // 先頭1byte保管
clientSTAT.TimeOut[num] = 0; // タイムアウトカウンター初期化
}
}
break;
case WStype_ERROR: Serial.printf("[%u] Socket error!\n", num);
break;
case WStype_FRAGMENT: Serial.printf("[%u] Fragment\n", num);
break;
case WStype_FRAGMENT_FIN: Serial.printf("[%u] Fragment Fin\n", num);
break;
case WStype_FRAGMENT_TEXT_START: Serial.printf("[%u] Fragment Text Start\n", num);
break;
case WStype_FRAGMENT_BIN_START: Serial.printf("[%u] Fragment Bin Start\n", num);
break;
case WStype_PING: Serial.printf("[%u] Ping\n", num);
break;
case WStype_PONG: {
Serial.printf("[%u] Pong\n", num); // 接続後にイベント発生
intervalFlag = true; // 最新インターバル告知要求
datasizeFlag = true; // 最新データサイズ告知要求
}
break;
}
}