マイコンに実装したWebSocketによる双方向通信【STM32Nucleo】
世に出回っているWebsocket情報のほとんどがWEB上での記事に関するもので、組み込みマイコンにWebSocketを適用した例はWiFiモジュールESPシリーズを搭載したAruduino系のものしか見当たりません。WEB上のものであれば、ファイルシステムが利用できるために、 Node.jsのようなプラットフォーム が使用できますが、 ファイルシステムのない組み込みマイコンでは採用できません 。また、Arduino系の開発環境であれば情報も豊富でライブラリが利用できますが、STM32マイコンのCortex-M3系では適用例があまりみあたらず、ライブラリ等はあることはありますが情報も少なく使いこなすことは困難のため、すべてを新規に構築する必要があるところが苦労した点です。
WebSocketを確立するまでの流れ
WebSocketを開始するきっかけ は クライアント側のブラウザから HTMLの中に埋め込んだ JabaScriptで記述したWebSocketリクエストを送る ことです。これはHTMLページを起動するときにリクエストを発行してもよいし、ページ内に作成した接続開始ボタンを押したときにリクエストを発行する仕様にしてもよいです。
WebSocket通信はしくみを理解するのはそう難しくはないですが、初めての実装は結構大変です。実装での 最大のヤマ はブラウザで発行された WebSocketキー からブラウザへ返す アクセスキーを生成 するところです。
アクセスキーさえ生成できれば、あとは HTMLとJavaScriptを駆使するだけ ですので組み込み系の人にとってはちょっとなじみは薄いかもしれませんがWEBプログラミングに精通している人ならばなんのことはないと思われます。
② サーバーはブラウザからGETメソッドを受けて、レスポンスを返すのですが、その中にJavaScriptで記述したWebSocketを起動するためのリクエストを埋め込んでおきます。 大事なポイント はここで一旦 通信を切断しておく ことです。
③ 数秒おいてからブラウザはサーバーから送られたレスポンス内のJavaScriptを実行して WebSocketキー を含んだ HTTPアップグレードリクエスト をサーバーに送ります。
④ サーバーではブラウザからのリクエストがWebSocketであることを認知すると WebSocketキー を抽出して アクセスキー を生成し、ブラウザに返します。
⑤ ブラウザではサーバーから返された アクセスキー が有効なものであると認定できると WebSocketコネクションを確立し、オープンイベントを発火 します。
コネクション確立までの流れは以上です。これで サーバーとブラウザはWebsocketプロトコルによる双方向通信ができる ようになります。あとは ノンブロッキングのソケット通信 となります。流れ自体は簡単なので理解はできると思いますがこれを実装するには結構な壁がありますので順次解説していきます。
コラムイベント駆動型プログラミング でイベントが起こることを 発火 というそうです。
WebSocketリクエストを埋め込んだウェブページWebSocketを開始する場合はブラウザからHTTPリクエストを受け取った後にレスポンスの メッセージボディにJavaScriptでWebSocketリクエストを埋め込む ところが通常の場合と異なります。
WebSocketリクエストを埋め込んだHTTPレスポンスWebSocketを開始するためのコードは大体フォーマットは決まっていて、WebSocketオブジェクトを生成することから始めます。 オブジェクト名 は 任意に指定 できます。下記のサンプル例では wsocket としています。
WebSocketリクエストコード ポイントWebSocketイベント は組み込みプログラミングにおける 割り込みのようなもの でWebSocketオブジェク生成時に同時に登録します。あらかじめイベントハンドラ内に定義しておいた内容は、WebSocket実行時、下記のイベントのたびに発生します。
- openイベント :onopenイベントハンドラプロパティ WebSocketのコネクションが開かれたときに発生
- closeイベント :oncloseイベントハンドラプロパティWebSocketのコネクションが切断したときに発生
- messageイベント :onmessageイベントハンドラプロパティWebSocketを通してデータを受信したときに発生
- errorイベント :onerrorイベントハンドラプロパティ WebSocketのコネクションがエラーにより切断したときに発生
WebSocketメソッド はブラウザ側から任意のタイミングで データ送信 および WebSocketコネクション切断 を行う場合に実行します。
- sendメソッド :wsocket.send(data)はデータを送信するためのもの
- closeメソッド :wsocket.close()はWebSocketコネクションを切断するためのもの
その他、接続状態を確認するためのプロパティ readyState があり、接続状態をモニターして切断時の処理などに利用できます。
0: CONNECTING まだコネクションが確立されていない状態1: OPEN コネクションが確立されている状態2: CLOSING コネクションが閉じる過程にある状態3: CLOSED コネクションが閉じている状態例:var connectionstate=wsocket.readyState //0 - 3
WebSocketキーからアクセスキー生成WebSocketコネクション確立のための最大のポイントがブラウザから渡された WebSocketキーからアクセスキーを生成 するところです。
ブラウザが発行するWebSocketキー囲んだ 24文字のコード がWebSocketコネクションのために ブラウザが発行したキー です。 一例としてサーバーは受信したGETリクエストの空白前のヘッド部に” Sec-WebSocket-Key ”があればWebSocketリクエストと認識し、その後に続く 24文字分のキー ( xxx. xxx== ) を抽出 します。
この次が最大のヤマ場であるリクエストの WebSocketキー に対する アクセスキー を生成する部分です。
ポイントアクセスキーの定義を初めて目にするといったい何のことかはわかりにくいのですが、解説すると、ブラウザから与えられたキーにG UIDと呼ばれるコードを連結 させたあとに、 SHA1方式で暗号化して20桁のハッシュ値を生成 し、さらに BASE64エンコードで符号化 するということです。
コラム初めてアクセスキー生成にチャレンジしたとき、まず ハッシュ 、 SHA1 や BASE64 といった言葉が何であるか分からなかったため、さっぱり理解できなかったです。 これらは暗号・符号化のための用語やツール だとわかってからは何をすべきかようやくわかったのですが、暗号化の内容自体は無機質な性質のものでいまだによくわかりません。
ハッシュ値およびBASE64エンコード値生成手順- ブラウザが生成するキー:リクエストヘッダから hqkH4S/djHSSovAPaDdycg== のみ抽出する
- GUID連結(標準関数strcatを使用):抽出したキー に GUID を連結 し、hqkH4S/djHSSovAPaDdycg== 258EAFA5-E914-47DA-95CA-C5AB0DC85B11 とする
- ハッシュ値SHA1(20桁40文字16進数表示)生成(ハッシュ化):GUID連結したキー を SHA1ハッシュ化 すると 1c10aa3dd498c5bfb39a95c5c10277e6770f28c1 (バイナリ/HEX)が得られる
- ハッシュ値20桁をBASE64エンコード(符号化): 20桁のSHA1ハッシュ値 を BASE64と呼ばれる符号化 をすると HBCqPdSYxb+zmpXFwQJ35ncPKME= (28文字テキスト)が得られる
ブラウザとサーバー間でやり取りするキーはテキスト ですが ハッシュ化処理はバイナリ(HEX) で行っていることを意識してください。
ポイント2WebSocketキーに対するアクセスキーの正当性 はネットで利用できる 変換ツールを使って 確認できます。
WebSocketコネクション確立レスポンスのフォーマットは下例のとおりでよいと思います。レスポンス一行目のステータスラインは"HTTP/1.1 101 Switching Protocols"と"HTTP/1.1 101 OK"のどちらでも機能するようです。
アクセスキーを付加したHTTPアップグレードレスポンス生成したアクセスキーが有効であることがブラウザで認識されると WebSocketコネクションが確立 し、WebSocketプロトコルによる双方向通信が始まります。いわゆる サーバーとクライアント(ブラウザ)間でハンドシェイク のやり取りが成立したということになります。
確立するとopenイベントが発火 し、 定義したイベント内容が実行 されます。例えばonopenイベントで”Websocket Connect!!"のメッセージを表示させる処理など記述しておけばよいでしょう。
WebSocketによるデータの送受信方法
WebSocketコネクションが確立すると 任意のタイミングでブラウザ、サーバー間で双方向通信ができる ようになります。WebSocket通信では送受信データには テキスト のみならず バイナリ も扱うことができます。
データは WebSocketデータフレーム とよばれるフォーマットに従ったものを 送受信時 に扱います。
データフレームのフォーマットは 1バイト単位のブロック で構成されています。1番目のバイトブロックは 通信データが最後のパケットであるかの指定 および データの種類を指定 します。2番目のバイトブロックでは データのマスク有無 および データ長 を指定します。
データのマスク有無 ですが、これは決まり事で ブラウザからのデータ は マスクを付加 し、 ブラウザへのデータ は マスクを付けない ことになっています。
ブラウザからサーバーへのデータ送信具体的な例としてブラウザが テキスト文字列"test"を送信 する場合で確認します。送受信はTCPソケット通信で行われます。
ブラウザからの送信では sendメソッドを使用 します。
sendメソッド wsocket.send(”test") を実行
4文字テキストの場合、ブラウザは 10バイト分のWebSocketデータフレーム を送信します。サーバー側で受信する TCPデータバッファをdata_buffer[] とすると実際にこの受信バッファに読み込まれるデータの例は以下のようになります。
ブラウザが”text”を送信した場合のデータフレーム4文字テキストデータ は 単独パケット なので1番目バイトブロック data_buffer[0] は 先頭ビットFINは1 、テキストデータのため opcodeは1 となるので16進数表記で 0x81 となります。
2番目バイトブロック data_buffer[1] は ブラウザからのデータでマスク付きなので先頭ビットMASKは1 、データの payload長は4 なので16進数表記で 0x84 となります。
3-6番目バイトブロック data_buffer[2] -[5] は ブラウザから付加されたマスクキー です。これらは同じデータを再度送っても都度変わります。
7番目以降のバイトブロック data_buffer[6] -[9] の文字数分がブラウザから送信するテキストデータにマスクでコード化されたものです。コード化したデータはマスクキーを使用して復号することで 抽出データunmasked_str[i] を取得します。
抽出データunmasked_str[i] は Mask_Key[i] と masked_data[i] の XOR (論理演算)によりUnMask(復号)して取得します。
unmasked_str[i]=Mask_Key[i % 4]^masked_data[i] よりデータ取得
ブラウザは任意のテキストデータやバイナリデータ以外にも送信するコードがあります。その一例として closeメソッドでwsocket.close()を実行した場合 のやり取りされるデータを確認してみます。
この場合のブラウザから送信されるデータ列は実データを含まない 6バイト分のデータフレーム です。最初のバイトブロックの opcodeがcloseメソッドを実行したときは0x8 となっています。マスクキーも送信されてきますがここでは意味はありません。
ブラウザでcloseメソッドを実行した場合のデータフレームサーバー側で受信した 先頭のバイトブロックdata_buffer[0]が0x88 であればブラウザで closeメソッドを実行 したことがわかります。
WebSocketの opcode には、他に 0x9(Ping) , 0xA(Pong) がよく使用されます。切断したときの処理などに利用できます。
ポイントWebSocket通信ではTCPソケット通信と同様に任意のタイミングでブラウザからのデータを受信することになるため、 TCP受信処理においてWebSocketデータに適切に対応 させて、 想定外のエラーを発生させない ようにしておきます。
サーバーからブラウザへのデータ送信今度はサーバー内の テキスト文字列"test"をブラウザに送信する場合で動作を確認 します。サーバーからの場合は マスクを必要としない のできわめてシンプルです。
4文字テキスト を送信する場合は マスクは使用せず 、 WebSocketデータフレーム の1番目と2番目バイトブロックに送信するデータのフォーマットを指定して、つづいてテキストデータを送信するだけです。
HTTPプロトコルに比べて送信時は特に ヘッダが小さいため送信する情報量が小さくてすむ のが特徴です。送信データが小さいものほど差は顕著です。
サーバーが"text"を送信した場合のデータフレーム サーバーからの送信コマンドブラウザでは データを受信したときにonmessageイベントが発火 しますので、テキストを表示させるなど実施したい処理を設定しておいてください。
WebSocketテストプログラム
ポイント前回の記事と同様にファイルシステムのないマイコンの場合ですので、プログラミングにおいてサーバからブラウザへ送信する HTMLはすべてハードコーディング して 配列に格納したもの です。
WebSocketコネクションを確立するために"Connect"ボタンを押します。このサンプルでは”Connect"ボタンを押してから WebSocketリクエスト を送っています。
HTMLのスライダーを操作すると JavaSriptで設定したデータ範囲の数値が"Slider output"に表示 されます。これはブラウザ側で生成した値で送信するデータですのでWebSocketコネクション確立にかかわらず表示されます。
WebSocketコネクションが確立した状態でスライダーを操作すると同時に "NUCLEO Loopback"に数値が表示 されます。これはサーバーが受信したデータをそのままブラウザに送り返しているデータですので双方のデータはほぼ リアルタイムに連動 しているところが WebSocketによる双方向通信 の特徴です。
"Close"ボタンを押すと closeメソッドを実行してコネクションを切断 するようになっています。Closeイベントが発火すると"Websocket DisConnected.."を表示するようにしています。
ポイント送信のサンプリングレートが150msより高速になると不安定でよく停止や切断をするようになりました。数値の桁が変わっても停止します。そこで対策としてブラウザへの送信データのバイトブロックを3回に分けていたのを すべて1つにまとめて1回の送信 にし 、数値の桁にかかわらずデータ長を3に固定 すると改善しました。
websocket_send(文字列に変換した3桁数値);
送信周期を100ms以下にすると不安定で、組み込み機器のリアルタイム通信としてはこの速度では少し物足りずWebSocketの利点が活かされていないようですが、現状ではこんなものかもしれません。フリーズや切断する原因を特定して可能であればより安定して高速な通信の実現を今後の課題としておきます。
コラム1WebSocketの技術はどちらかといえば、組み込み系のものではなく、HTMLやJavaScriptを駆使するWEBエンジニアが得意とする分野のものであり、これを組み込みに適用しようとしたから苦心しただけのような気はします。とはいえ、マイコンプログラミングでは必須の デバッガ や パケットモニター を使用すれば、ブラウザとマイコン間でやりとりされる データが実際に確認できる ために デバッグとともに理解が深まる ともいえます。
コラム2WebSocket通信はHTTPプロトコルと比較して、やりとりするパケットが小さいために 使用するメモリの消費も小さくなる傾向 はあります。Nucleo-F103RBのような メモリの小さなマイコンでも十分機能 しています。今回のサンプルプログラムでのメモリ消費はRAMで約9k、Flashで約28k程度です。
Most Powerful Bible to Become an Embedded Engineer Bidirectional communication via WebSocket on Microcontroller[STM32Nucleo] | M. https://en.depfields.com/websocket/In the previous article, I explained how to develop IoT for devices more closely by installing a web server in MCU and accessing it from a browser such as a PC or smartphone without using a dedicated application.Browsers usually commu
関連記事を表示
2022年2月18日 2021年12月22日 2021年12月19日 2021年12月18日 2020年9月22日 2020年9月21日 2020年9月12日 2020年9月21日 2020年8月30日 HTTPプロトコルで構成したWEBサーバーを搭載したマイコンシステム【STM32Nucleo】 2021年12月22日 マイコンに実装したWebSocketの超高速リアルタイムモニター【STM32Nucleo】 2022年2月18日 サイト内検索 最近の投稿 2023年5月22日 2022年4月29日 2022年4月11日 2022年2月18日 2022年2月14日 2021年12月22日 2021年12月19日 2021年12月18日 2021年7月12日 2021年7月11日 2021年3月22日 カテゴリー- お知らせ (2)
- 最新記事 (2)
- ARMマイコン入門 (88)
- 組み込みマイコンスキル全般 (47)
- 組み込みに使われるマイコン (7)
- 組み込み技術の基本 (6)
- マイコン周辺機能(ペリフェラル)とは (3)
- 各周辺機能(ペリフェラル)詳細 (11)
- 組み込みで使うOS (4)
- 組み込み開発設計 (7)
- マイコンのIoT化 (7)
- 汎用入出力を使ったアプリ (2)
- タイマを使ったアプリ (5)
- SYSTIC割り込みを使ったアプリ (1)
- 外部割り込みを使ったアプリ (1)
- シリアル通信USARTを使ったアプリ (8)
- シリアル通信I2Cを使ったアプリ (1)
- シリアル通信SPIを使ったアプリ (1)
- ADコンバータを使ったアプリ (4)
- RTOSを使ったアプリ (4)
- 計測アプリ (8)
- IoTアプリ (4)
- 実践で使う制御理論 (7)
- モーションコントロールアプリ (6)
Copyright © 即戦力モノづくり!エンジニアへの道標 All Rights Reserved.
- 組み込みマイコンスキル全般 (47)