ScratchXとESP8266をWebSocketでつないで遊んでみる。

More than 3 years have passed since last update.


これはなに?

別の記事でScratchXからejectしてみる 極めて実用性の高い 作例を紹介したのですが、もう少し別の遊び方をしてみようかと思います。

Arduino向けライブラリで、WebSocketもJSONもよろしく使えるので、

これを使って、ScratchXから単純なリモートセンサーと戯れてみます。


偽PicoBoardを作る

Scratch用にPicoBoardというセンサーボードがあるので、

このScratch-APIと互換性のある実装してみようかと思います。

ただし、PicoBoardはデジタル1系統、アナログ7系統のIOがあって、それに対して、ESP8266は

デジタル11系統、アナログ1系統なので、デジタルの方は問題ないですが、アナログの入力数が圧倒的に不足です。

ちゃんとやるなら、WiFi Shieldとか使えばいいんですが、生モジュールで500円のESP8266と比べると、

お値段がちょっとお高い感じ。

今回は、端折ってアナログ1系統、デジタル1系統を使って、ボリュームとボタンだけ使えるようにしてみます。


実施例


ESP8266 向け Sketch

https://github.com/soburi/scratchx-websocket/blob/gh-pages/Arduino/FakePicoWS/FakePicoWS.ino


回路図

Untitled Sketch 2_bb.png

回路図。宇野さんは代理人です。ESP-WROOM-02 Arduino互換ボード を使っています。ピンの位置だけ参考に。

オリジナルのPicoBoardはアナログに4つ、ワニ口クリップがささるようになってますが、今回はそれは割愛。


  • A0 - スライダー

  • IO0 - ボタン

に割り付けています

SSID/PASSを書き換える必要があります。これも環境にあわせて変更してください。

WiFiに接続すると、シリアルに設定されたIPアドレスが表示されます。

Scratch側からはこのIPに接続します。


ScratchXのプラグイン

http://scratchx.org/?url=http://soburi.github.io/scratchx-websocket/examples/fake-picoboard.sbx

'ESP8266's IP Address'にESP8266で表示されたIPを設定します。

スライダーを動かすと猫様が回転し、ボタンを押すと猫様が前身します。


通信

ESP8266からこんなJSONをバンバン投げて現在の状態を通知しています。

{

"notify": {
"slider": 123,
"button": 1
}
}

オリジナルのPicoBoardがInputだけなので、今のところScratch側からの

送信はやってません。ESP8266からデータもらうだけです。


ESP8266側

ADCとGPIOの状態を定期的に見に行って、

状態変わっていたらJSONを送信します。


基本の入出力

基本的なADC, GPIOの使い方は Arduinoのチュートリアル

https://www.arduino.cc/en/Tutorial/AnalogReadSerial

https://www.arduino.cc/en/Tutorial/Button

あたりを参考に。

ボタンについては、

http://mag.switch-science.com/2013/05/23/input_pullup/

この記事も参考になります。


WebSocketServerを立ち上げる

WebSocketは、

https://github.com/Links2004/arduinoWebSockets

のライブラリを使います。ライブラリマネージャからWebSocketで検索して、

そこからインストールできます。

骨子はWebSocketServerのサンプルで、

https://github.com/Links2004/arduinoWebSockets/blob/master/examples/WebSocketServer/WebSocketServer.ino

ほぼそのまま使えます。

処理の流れは



  1. WebSocketServerオブジェクトを生成


  2. setup()WebSocketServerにコールバックを登録


  3. loop()WebSocketServer::loop()を定期的に実行

  4. コールバックにWStype_CONNECTEDが通知されたら、接続元IDを記憶して通信を開始

接続できたら、



  1. loop()で自発的に送信

  2. コールバックにWStype_TEXT, WStype_BINが来た時に、応答する

のいずれかで送信受信を行います。

今回は、センサーの値の検出で一方的に送信しようとしているので、

loop() で送信処理を行います。

BoilerPlate的なサンプルはこんな感じになります。(ほぼExampleのまんまです)

#include <ESP8266WiFi.h>

#include <ESP8266WiFiMulti.h>
#include <WebSocketsServer.h>

void webSocketEvent(uint8_t num, WStype_t type, uint8_t * payload, size_t length);

ESP8266WiFiMulti WiFiMulti;
WebSocketsServer webSocket = WebSocketsServer(80);

void setup() {
/* WiFi 接続 */
WiFiMulti.addAP("SSID", "passpasspass");

while (WiFiMulti.run() != WL_CONNECTED) {
delay(100);
}

Serial.print("WiFi connected: ");
Serial.println(WiFi.localIP());

/* WebSocketServer初期化 */
webSocket.begin();
webSocket.onEvent(webSocketEvent);
}

void loop() {
webSocket.loop();
/* 自発的に送信する処理を書く */
}

void webSocketEvent(uint8_t num, WStype_t type, uint8_t * payload, size_t length) {
switch (type) {
case WStype_DISCONNECTED:
break;
case WStype_CONNECTED:
break;
case WStype_TEXT:
/* 応答処理を書く */
break;
}
}


スライダー・ボタンの値を読み取って、JSONを送信する

スライダーやボタンの値を見るのはloop()の中でやりますが、

WebSocketServer::loop()を呼ぶのをサボれないんで、delay()は使えません。

時間経過見るなりタイマ使うなりで、適当に処理を間引きます。

ここではあまり頭使わず時間経過見て判定しています。

int now = millis();

if ( (last_update + SEND_INTERVAL) < now) {
last_update = now;
int A0_current = analogRead(A0);
int IO0_current = digitalRead(0);
...

JsonはArduinoJsonライブラリ

https://github.com/bblanchon/ArduinoJson

が使えます。これも、ライブラリマネージャからインストールできます。

スクリプト言語のhashっぽい記法でわりとわかりやすいライブラリなんですが、

どうも再利用しようとすると妙な動きするので、スタック上で使い捨てています。

printTo() で文字列に吐き出します。

最後にWebSocketServer::sendTXT()で文字列をScratch側に送ります。

  StaticJsonBuffer<128> notifyBuffer;

JsonObject& root = notifyBuffer.createObject(); // { }
JsonObject& notify = root.createNestedObject("notify"); // { "notify" : {} }
notify ["slider"] = (int) (A0_current / 1024.0 * 100); // { "notify" : { "slider": 123 } }
notify ["button"] = (IO0_current ? 0 : 1); // { "notify" : { "slider": 123, "button": 1} }

char sendtext[128] = {0};
root.printTo(sendtext, sizeof(sendtext) );

webSocket.sendTXT(client_no , sendtext);


Scratch側

ESP8266からバンバン値が上がってくるので、通知でテーブルにキャッシュしています。

あとは、Scratch側から要求あったときに値返すだけです。


ブロック

ブロックの定義は、オリジナルのPicoBoardから引き写して、

['b', 'sensor %m.booleanSensor?', 'getSensorBoolValue'],

['r', '%m.sensor sensor value', 'getSensorValue'],
['h', 'when %m.booleanSensor', 'isButtonPressed'],
['h', 'when %m.sensor %m.lessMore %n', 'compareSensorValue'],

fakepico.png

のような感じになります。

日本語で表示できているのは、オリジナルのPicoBoardの翻訳リソースが使われているため。


データ受信

ブロックからの動作は、キャッシュしている値を返す動作なので、

データ受信イベント動作はAPIには見えない内部の動作です。

データ受信の通知で、センサの項目ごとに更新しています。

(通信部分をライブラリ化していて、イベントは付け替えて再送信しています)

ext.api.addEventListener('message-received', function(event) {

let recv = JSON.parse(event.data);
if(recv.notify != undefined) {
for(k in recv.notify) {
state_cache[k] = recv.notify[k];
}
}
});


レポーター、ブーリアンブロック

レポーターブロックへは、単純にキャッシュした値を返します。

受信前は未定義になりますので、空文字列を返しています。

Scratchではエラー処理機構が無いので、この辺の判断は難しいです。

ブーリアンブロックも同じ動作ですが、型がブール指定なので、

true or falseに強制しています。

ext.getSensorValue = function(prop) {

let key = proptable[prop];
let state = state_cache[key];
if(state == undefined) return "";

return state;
};

ext.getSensorBoolValue = function(prop) {
return (ext.getSensorValue(prop) ? true : false);
};


ハットブロック

ハットブロックはセンサの値が指定の条件を満たしているかをブール値で返す実装にします。

Scratch側のほうで、この判定関数の値が falseからtrueに変わった場合に

ぶら下がるブロックの処理を行うようになっています。

ext.isButtonPressed = function(prop) {

let key = proptable[prop];
let state = state_cache[key];
if(state == true) {
return true;
}

return false;
};

ext.compareSensorValue = function(prop, lessmore, threshold) {
let key = proptable[prop];
let state = state_cache[key];
if( (lessmore == '<') && (state < threshold) ||
(lessmore == '>') && (state > threshold) ) {
return true;
}

return false;
};


まとめ


  • ESP8266を使うと、WiFiに接続できる簡易なリモートセンサーが作れます。

  • ライブラリを使うとArduinoでも(そこそこ)簡単にJSONとWebSocketが扱えます。

  • ScratchXからWebSocketで通信ができるので、これらを組み合わせると、Scratchから色々と無線でいじって遊べます。

  • ScratchのAPIを作ると、自作プログラムに「小学生でも使える」という非常に煽り性の高い形容詞を使うことができるようになります。