前回に引き続き、Arduino化した ESP-WROOM-02 ( ESP8266 )でスマホブラウザとの双方向ストリーミング通信 WebSocket を実現するためのデータ通信の方法を私なりの独自解釈で解説していきたいと思います。
前回はハンドシェイク(コネクション確立)まででしたが、今回はスマホとWROOMの双方向データ送受信方法について説明します。
スマホブラウザからWROOMへデータ送信方法
ハンドシェイク確立したら、いつでもデータを送信できる状態になります。
例えば、ブラウザのソースHTMLタグでマウス用スライダーを表示させます。
これは、前回の記事の始めの方のHTMLソースコードの78~81行に相当します。
以下はマウスで操作する場合です。
<input type="number" name="v_box" id="v_box" style="width:60px"> <input type="range" name="slider" onmousemove='document.getElementById("v_box").value=this.value;'>
また、スマホなどのタッチパネルでスライダー数値を表示させる場合は以下の様になります。
<input type="number" name="v_box" id="v_box1" style="width:60px"> <input type="range" name="slider" ontouchmove='document.getElementById("v_box1").value=this.value;'>
マウスの場合、onmousemove
、スマホの場合、ontouchmove
、ということです。
ESP-WROOM-02をWebSocketで動かす場合はontouchmove
しか使わないと思います。
このスライダーで得た値をWROOMへ送信する場合は、JavaScript内で生成したWebSocketオブジェクト
var wsUri = "ws://xxx.xxx.xxx.xxx"; websocket = new WebSocket(wsUri);
を使って、関数functionを作成します。
ws://xxx.xxx.xxx.xxx
のところで、ご自分のルーターのローカルIPアドレスを入力します。
ハンドシェイク確立したら
websocket.onopen();
という文でオープン開始イベントは発行します。そうしたらデータ送信可能ということになります。
データは以下の指令でWROOMへ送信します。
websocket.send(data);
dataのところにJavaScriptの構文に従ってデータを割り当てます。
以上をまとめた簡単なHTMLソースはこんな感じになるかと思います。
(これだけで動作するわけではありません。動作するかもしれませんが・・・)
<script language="javascript" type="text/javascript"> var wsUri = "ws://xxx.xxx.xxx.xxx"; var websocket = null; websocket = new WebSocket(wsUri); function doSend(data) { websocket.send(data); } </script> <input type="number" name="v_box" id="v_box" style="width:60px"> <input type="range" name="slider" ontouchmove='doSend(this.value); document.getElementById("v_box").value=this.value;'>
以上をスマホブラウザのHTMLファイルにうまく盛り込んでいくわけです。
前回の記事でも記載しましたが、WebSocketコネクションからクローズまでのすべてのイベントを盛り込んだHTMLファイルの例が以下のようになります。
<!DOCTYPE html> <html> <head> <meta charset='utf-8'> <meta name='viewport' content='initial-scale=1.1'> <title>WebSocket Test</title> <script language='javascript' type='text/javascript'> var wsUri = 'ws://192.168.0.21'; var output; var websocket = null; function init() { output = document.getElementById('output'); testWebSocket(); } function testWebSocket() { if(websocket == null){ websocket = new WebSocket(wsUri);//WebSocketオブジェクト生成 websocket.onopen = function(evt) { onOpen(evt) }; websocket.onclose = function(evt) { onClose(evt) }; websocket.onmessage = function(evt) { onMessage(evt) }; websocket.onerror = function(evt) { onError(evt) }; } } function onOpen(evt) { writeToScreen('CONNECTED'); doSend('WebSocket rocks'); } function onClose(evt) { writeToScreen('WS.Close.DisConnected'); websocket.close(); websocket = null; } function onMessage(evt) { var ms1 = document.getElementById('WROOM_DATA'); ms1.innerHTML = evt.data; } function onError(evt) { writeToScreen("<span style='color: red;'>ERROR:</span> " + evt.data); } function doSend(data) { websocket.send(data); } function WS_close() { websocket.close(); websocket = null; } function writeToScreen(message) { var msg = document.getElementById('msg'); msg.innerHTML = message; } window.onload = function() { setTimeout('init()', 3000); } </script> </head> <body> <h2 style='color:#5555FF'> <center>ESP-WROOM-02(ESP8266) WebSocket Test</center></h2> from WROOM DATA = <font size=4> <span id='WROOM_DATA' style='font-size:45px; color:#FF0000;'></span> <br>JS-innerHTML= <input type='number' name='v_box' id='v_box' style='width:30px'> <br><br> <center>LED dimming <input type='range' name='slider' ontouchmove="doSend(this.value); document.getElementById('v_box').value=this.value;"> </center> <br><br> <div id='msg' style='font-size:25px; color:#FF0000;'> </div><br> <input type='button' id='WS_close' value='WS.CLOSE' style='width:150px; height:40px; font-size:17px;' onclick='WS_close()'> <br> </body> </html>
WROOMでWebSocketデータを受信する
ブラウザから送られてきたデータはフレーム形式となっています。
ネットで出ている情報を読み込んでも最初はサッパリ分かりませんでしたが、ひたすらデータを受信してみて、ネットの情報と照らし合わせてみて何とか解明しました。
上記のHTMLスライダーを動かすと、
数値100 → 3文字
数値10~99 → 2文字
数値0~9 → 1文字
という風に送る文字列のサイズが決定されて、送られてきます。
WebSocketでは1回に127文字を送ることができ、さらにオプションで文字サイズを延長できて一度に送ることが出来ます。また、データを連結して送ることもできます。
しかし、今回は上記のJavaScript例では、スライダー数値が決定次第1~3文字単位でフレームを閉じて送られてくるので、細切れデータが怒涛のように送られてくることになります。
チャットとは異なり、スライダーのようなデータ追従の場合はこの細切れデータの方が良いわけです。まとめて送るとリアルタイム性が無くなりますからね。
では、送られてきたデータを見てみましょう。
スライダーを0にすると、この7バイトが連結してWROOMに送られて来ることになります。
つまりシリアル通信ではこんな感じの連続データです。矢印の方向がデータの送信方向です。
←←10000001100000011111101111000111110000001010001011001011
これをWROOMで1バイト毎にclient.read()関数で読み込みます。
読み込んだデータを2進数で表示させると以下のようになります。
1バイト目 10000001---Fin=1,opcode=1=テキスト 2バイト目 10000001---マスク有効、文字サイズ1 3バイト目 11111011---mask-key[0] 4バイト目 11000111---mask-key[1] 5バイト目 11000000---mask-key[2] 6バイト目 10100010---mask-key[3] 7バイト目 11001011---mask-data[0]
●1バイト目の 8bit 目が先頭データですが、Fin というフラグで、1だと次のデータを連結せず、単独データでフレームを閉じるという意味になります。
0~4bit が opcode というもので、0001 ならばテキストデータということです。opcode を変えるとバイナリデータを送ることやpingを送信したりすることもできます。
●2バイト目の 8bit 目が1ならばデータがマスク(暗号化)されているということです。WebSocket ではクライアントからのデータ送信は必ずマスクされています。サーバーからのデータ送信はマスクしていなくても良いです。
0~7bit が文字サイズです。0~126 文字ならばそのまま2進数表示されます。それより大きい文字列を一気に送る場合はネットでググって参照してください。ここでは127文字以内で十分です。
●3バイト目~6バイト目はマスクキーです。これを使ってマスクされたデータを復号します。
●7バイト目がマスク(暗号化)された実データです。
マスクデータを復号して、実データを抽出する
では、7バイト目のマスクデータを復号して、実データを抽出していきましょう。
3バイト目をマスクキーのゼロ番目として、7バイト目をマスクデータのゼロ番目とすると実データの抽出は以下の式になります。
[]でくくられた数値を配列の数値のようにi番目のデータとして、
実データ[i] = mask-key[i % 4] ^ mask-data[i];
となります。
これの意味は、i番目のマスクされたデータを
i % 4 (i を4で割った余り)番目のマスクキーでXOR演算すると実データが得られるということです。
つまり、マスクデータは1桁ですからゼロ番目というで、i=0; となります。
すると、
0 % 4 = 0
なので、mask-key[0]を使います。
よって、
実データ = 11111011 ^ 11001011 = 00110000 = 0x30
となります。
16進数の 0x30 は ASCII文字では ‘0’ ですので、これで実データを抽出できたということになります。
ん~~・・・難しいですねぇ・・・。
これの意味を理解するまでかなり苦労しました。
でも、これが分かれば一度プログラムを組んでしまえば流用するだけですね。
因みに、参考に実データが9文字の場合は
10000001---Fin=1,opcode=1=テキスト 10001001---mask有効,文字数9 10010001---mask-key[0] 01000110---mask-key[1] 01001010---mask-key[2] 11110001---mask-key[3] 11100100---mask-data[0] 00101000---mask-data[1] 00101110---mask-data[2] 10010100---mask-data[3] 11110111---mask-data[4] 00101111---mask-data[5] 00100100---mask-data[6] 10010100---mask-data[7] 11110101---mask-data[8]
というデータになります。抽出した文字列は ”undefined” となります。
WROOMからスマホにデータを送信する
データの送信は受信より簡単です。マスクしなくて良いのです。
例えば、1文字送信ならば
client.write(B10000001); client.write(B00000001); client.print('a');
というふうに送れば、ブラウザで認識してくれます。
1行目の 10000001 はFin=1で、連結データはなく、テキストデータという意味です。
2行目の 00000001 はマスク無しの1文字だけ送るということ。
3行目は見ての通りで、’a’ という文字を送るということです。
Arduino化した ESP-WROOM-02 ではスケッチ(プログラム)上の制約上、双方向ストリーミング通信といえども任意のデータを受信してからデータ送信という風に同時通信はできません。時間差通信ということになります。 ですが、見かけ上、送受信同時というふうに見えるだけです。
ですから、データを送信しているときに多量のデータを受信するとオーバーフローを起こして、意図しない切断をされてしまいます。。
その場合はプログラムで適度にデータを間引く必要があると思います。
この辺はまだまだ研究中です。
以上でWebSocketのデータ送受信方法の概要でした。
次回は実際に工作してLEDを点灯させる方法を紹介していきたいと思います。
ではまた・・・。
Amazon.co.jp 当ブログのおすすめ
コメント