世の中、同じようなことを考える方はいるようで、すでにESP8266+ArduinoIDEでWebSocketライブラリが存在しています。WebSocketが扱えるようになれば、(プログラミングスキルがあれば)クラウド側から好きなタイミングでESP8266に指示をおくることができるようになるはずです。(が、世の中そんなに甘くないんですねぇ〜)
で、今回見つけたWebSocketライブラリは ESP8266-Websocket です。
今回の記事はこれのサンプルを動かしてみよう、と苦戦した記録です。作業PCのOSはUbuntu14.10 LTS 64bit、ArduinoIDEのバージョンは1.6.5です。なお、Ubuntuで動かすにあたっては、
$ sudo addgroup foobar dialout ユーザー `foobar' をグループ `dialout' に追加しています... ユーザ foobar をグループ dialout に追加 完了。 $
として、/dev/ttyUSB0のアクセス権をユーザーにつけた後、再ログインしないと/dev/ttyUSB0へアクセスできないとしてエラーになります。
Arduinoのライブラリは ~/Arduino/librariesの下に置けばいいようですので、そこにgitでライブラリを引っ張ってきます。
~$ cd ~/Arduino/libraries ~/Arduino/libraries$ git clone https://github.com/morrissinger/ESP8266-Websocket Cloning into 'ESP8266-Websocket'... remote: Counting objects: 286, done. remote: Total 286 (delta 0), reused 0 (delta 0), pack-reused 286 Receiving objects: 100% (286/286), 105.19 KiB | 0 bytes/s, done. Resolving deltas: 100% (103/103), done. Checking connectivity... done. ~/Arduino/libraries$
その後、ArduinoIDEで「スケッチ」→「Include Library」→「Manage Libraries」で右上の「Filter your search」で「ESP8266-Websocket」と入力して「INSTALLED」になっていることを確認します。
次に、WebSocketClientのデモを動かしてみます。
「ファイル」→「スケッチの例」→「ESP8266-Websocket」→「WebSocketClient_Demo」を選択します。
ソースの冒頭の
const char* ssid = "SSID HERE"; const char* password = "PASSWORD HERE";
の部分に自分の無線LANのESSIDとパスキーを入力してから、名前を付けて保存します。
このライブラリはおそらくWindows環境で作成されたのでしょう。Linux環境でビルドしようとすると、ファイル名の大文字小文字で失敗します。そのため、ライブラリの中の以下の部分を修正します。
WebSocketClient.h と WebSocketServer.h の
#include "String.h"
を
#include "string.h"
に、
WebSocketServer.cpp と WebSocketClient.cppの
#include "base64.h"
を
#include "Base64.h"
とします。
これでビルドが通るようになりましたが、それでもコネクションに失敗します。禁断のgoto文で接続しに行く部分のみを繰り返すと、何回目か以降は通るようです。ライブラリのWebSocketClient.cppの冒頭にデバッグのための#define文があるので、ここをコメントを外すとデバッグメッセージが出るのですが、これでもハンドシェークの途中で止まっていることはわかるものの、それ以上はなんともわかりません。
これ以上は間にLANアナライザを入れてみてパケット解析しつつ、RFCを読んで何かおかしいのか調べるしかなさそうです。
以下は、現時点でのサンプルプログラムを改造したものです。
#include <ESP8266WiFi.h> #include <WebSocketClient.h> const char* ssid = "xxxxxxxxxxxxxxxx"; const char* password = "xxxxxxxxxxxxxxxx"; char path[] = "/"; char host[] = "echo.websocket.org"; WebSocketClient webSocketClient; // Use WiFiClient class to create TCP connections WiFiClient client; void setup() { Serial.begin(115200); delay(10); // We start by connecting to a WiFi network Serial.println(); Serial.println(); Serial.print("Connecting to "); Serial.println(ssid); WiFi.begin(ssid, password); while (WiFi.status() != WL_CONNECTED) { delay(500); Serial.print("."); } Serial.println(""); Serial.println("WiFi connected"); Serial.println("IP address: "); Serial.println(WiFi.localIP()); delay(5000); RETRY: // Connect to the websocket server IPAddress remote_addr; if(!WiFi.hostByName(host,remote_addr)){ Serial.println("Cannot resolve hostname"); while(1){} } else { Serial.println("hostname resolved"); } if (client.connect(remote_addr, 80)) { Serial.println("Connected"); } else { Serial.println("Connection failed."); goto RETRY; while(1) { // Hang on failure } } // Handshake with the server webSocketClient.path = path; webSocketClient.host = host; if (webSocketClient.handshake(client)) { Serial.println("Handshake successful"); } else { Serial.println("Handshake failed."); while(1) { // Hang on failure } } } void loop() { String data; if (client.connected()) { webSocketClient.getData(data); if (data.length() > 0) { Serial.print("Received data: "); Serial.println(data); } // capture the value of analog 1, send it along pinMode(1, INPUT); data = String(analogRead(1)); webSocketClient.sendData(data); } else { Serial.println("Client disconnected."); while (1) { // Hang on disconnect. } } // wait to fully let the client disconnect delay(3000); }
WebSocketClient.cppをみると、サーバ側からのヘッダの解釈の実装がまだまだであることがわかります。ひとつの可能性として、どうやらここで止まっていそうです。もうひとつは、パケットの受信バッファの処理がどうも変な感じがします。
いずれにせよ、このライブラリはまだそのままでは使えるものではなさそうな感じです。(ま、よくみると色々と制約があるよ、って書いてあるんですけどね)