見出し画像

【SALZmini2022作製日誌】BLEレシーバーテスト版できた!

はじめに

前回は自動水やり装置SALZmini2022をChromeBookと接続させるため、自分の薄い理解の範囲でもがきながらネットの情報をつなぎ合わせていました。

ブラウザで提供されているWeb Bluetooth APIを活用して、M5StampC3とBLE通信するためのテストをしていました。

結果は成功。ラッピングライブラリのBlueJellyを用いたサンプルとESP32のBLEサンプルをマリアージュさせて今後の可能性を広げました。

さて今回はその可能性を元に実際に動くものを作りたいと思います。欲張ってしまうといつまでたっても完成しないので、

今回作製するものの範囲を最初にしっかり決めておこうと思っています。

・必要十分な仕様を決める。
・サンプルプログラムを最大限に活用する。
・見た目にはこだわらない。

と言うわけで作製タイトルも「BLEレシーバーテスト版」としました。

現在、SALZmini2022は下記の情報を発信しています。

wl:水分量%(1byte)
st:状態(1byte)
 c:カウンタ(2byte)

状態:
 0:ST_ERROR
 1: ST_WATERING
 2: ST_WAIT_RED(0<=水分量%<40)
 3: ST_WAIT_YELLOW(40<=水分量%<60)
 4: ST_WAIT_BLUE(60<=水分量%<100)

これらの情報はBLEアドバタイズ通信で常に発信されています。今回、これらの情報はもちろん表示したいのですが、それ以外にも接続したときのみ表示できる詳細情報も考えてみたいと思います。

例えば、過去数十件の水分量%履歴を保持していてそれを読み込みグラフを作成するとか。水分量%のキャリブレーションのため現在設定されている抵抗値の値を返すとか。

現時点でどれぐらいの情報をどんな形で通信させるのかわかっていません。作りながら考えて行こうと思います。

また、レシーバーからSALZmini2022に向かってコマンドを発信してみたいです。「今すぐ水やり」、「水分計MAX設定」、「水分計MIN設定」が思いつきます。

おおよその仕様が決まったところで、次のステップに入ります。

前回の最後の実験 Write.html を元にソースに手を入れていきます。
VSCodeでWrite.htmlを名称変更し、SALZminiReceiver.htmlを作り、フロントエンド部分の改造を加えていきます。

初期画面

このようにフロントエンドから作っていくとNotifyボタンも欲しくなり、Notify.htmlも参考にしながらボタンの追加を行いました。

おおよその形が出来上がったところで、今度はM5StampC3側のプログラムに手を加えます。

前回のテストではBLE_write.inoをそのまま使用していました。最終的にはSALZmini2022とすり合わせていく必要があるため、両方のソースを見ながらテストプログラムを書いていきます。

形になるまでは今稼働しているM5StampC3には手を付けず、テストプログラムを作り込んでいくことにします。

BLE_write.inoとSALZmini2022.inoを比較します。

BLE_write.inoはsetup()関数の中で定義を終え、必要な処理はコールバック関数で行っています。
SALZmini2022.inoは一定間隔ごとに呼ばれるloop()関数の中で処理を行っています。

共通:


BLEDevice::init("UUID名");
BLEServer *pServer = BLEDevice::createServer();

BLE_write.ino:

BLEService *pService = pServer->createService(...);
BLECharacteristic *pCharacteristic = pService->createCharacteristic(...);

pCharacteristic->setCallbacks(new MyCallbacks());

pCharacteristic->setValue("Hello World");
pService->start();

BLEAdvertising *pAdvertising = pServer->getAdvertising();
pAdvertising->start();

SALZmini2022.ino:

BLEAdvertising *pAdvertising = pServer->getAdvertising();
BLEAdvertisementData oAdvertisementData = BLEAdvertisementData();
oAdvertisementData.setFlags(0x06); // BR_EDR_NOT_SUPPORTED | LE General Discoverable Mode

std::string strServiceData = "";
strServiceData += (char)0x07;   // 長さ
strServiceData += (char)0xff;   // AD Type 0xFF: Manufacturer specific data
strServiceData += (char)0xff;   // Test manufacture ID low byte
strServiceData += (char)0xff;   // Test manufacture ID high byte
strServiceData += (char)wl;     // 水分量%
strServiceData += (char)st;     // 状態
strServiceData += (char)(c & 0xff);         // カウントの下位バイト
strServiceData += (char)((c >> 8) & 0xff);  // カウントの上位バイト

oAdvertisementData.addData(strServiceData);
pAdvertising->setAdvertisementData(oAdvertisementData);

pAdvertising->start();                             // アドバタイズ起動
delay(SECONDS(10));
pAdvertising->stop();

このように見てみるとなんとかまとめ上げることができそうです。
そこにble_notify.inoの機能も擦り合わせていきます。

違いは
BLE_notify.ino:

// Create the BLE Device
BLEDevice::init("ESP32");

// Create the BLE Server
pServer = BLEDevice::createServer();
pServer->setCallbacks(new MyServerCallbacks());

// Create the BLE Service
BLEService *pService = pServer->createService(SERVICE_UUID);

pCharacteristic = pService->createCharacteristic(
                      CHARACTERISTIC_UUID,
                      BLECharacteristic::PROPERTY_READ   |
                      BLECharacteristic::PROPERTY_WRITE  |
                      BLECharacteristic::PROPERTY_NOTIFY |
                      BLECharacteristic::PROPERTY_INDICATE
                    );

です。
他にも、数か所で違いがありました。
何となく2つの違いが見えてきたので、とにかく混ぜ込んで一つのプログラムに仕立てました。

シリアルモニタの様子。SALZmini2022とWriteの機能は動いているっぽい。

notify部分について作り込んでいきます。
SALZminiReceiver.html側での処理を確認します。

<div id="data_text"> </div>

が、別項目に変わっていて表示できていませんでした。
その部分を調整し、

何となく動いています。

その後ごにょごにょすり合わせを行い、なんとかプログラムをでっちあげることができました。

水分量%が表示されました!

その後もごにょごにょやっていて、とりあえずのでっちあげ版を作成しました。

今回苦労したのはSALZmini2022.inoから発信するnotifyデータです。

typedef struct {
  uint16_t dummy;     // 2:ダミー(なぜかゴミが入るので用意)
  uint16_t count;     // 2:カウント
  uint8_t waterLevel; // 1:水分量%
  uint8_t status;     // 1:状態
} MyData;

MyData myData;

loop()の中で
    pCharacteristic->setValue((uint8_t*)&myData, sizeof(MyData));
    pCharacteristic->notify();

を行い、SALZminiReceiver.htmlで、

//--------------------------------------------------
//Read後の処理:得られたデータの表示など行う
//--------------------------------------------------
ble.onRead = function (data, uuid){
  //フォーマットに従って値を取得
  let dummy, count, waterLevel, status;
  dummy = data.getUint16(0);
  count = data.getUint16(1);
  waterLevel = data.getUint8(4);
  status = data.getUint8(5);

  //コンソールに値を表示
  //console.log(count);

  //HTMLにデータを表示
  document.getElementById('uuid_name').innerHTML = uuid;
  document.getElementById('water_level').innerHTML = "水分量%:" + waterLevel;
  document.getElementById('st').innerHTML = "装置の状態:" + status;
  document.getElementById('count').innerHTML = "カウント:" + count;
}

という形で受けています。

  let dummy, count, waterLevel, status;
  dummy = data.getUint16(0);
  count = data.getUint16(1);
  waterLevel = data.getUint8(4);
  status = data.getUint8(5);

ここがよくわかっていない部分で、count, waterLevel, statusの値を正常に受け取るために、dummyを入れています。dummyを取ってしまうと、値が化けてしまう不具合を取り切ることができませんでした。

ともかく、モックアップ版という位置づけでやりたいことができる作りにしてみました。

次はコマンドの送信です。

文字列が送れるのは確認できています。
どんなコマンドで、どんな作り込みにするか。

コマンドの仕様①:

Watering :今すぐ水やり
Min:1234 :最小値設定
Max:2345 :最大値設定

ひとまずこれでやってみます。
この機能を実装するためには、現在の最小値、最大値が必要になります。
そう思い、元のソースを眺めました。

int
WlRate(int wl)
{
  //校正済の水分%を返す。
  //センサーを水に浸けた状態:1900
  //センサーを外に出した状態:2500
  const int wetWl = 1900;
  const int dryWl = 2500;
  int ret = (int)((1.0 - ((float)(wl - wetWl) / (float)(dryWl - wetWl))) * 100.0);

  return (ret < 0) ? 0 : ((ret > 99) ? 99 : ret);
}

const int wetWl = 1900;
const int dryWl = 2500;

この部分を校正できた方がいいか?
と思ったのですが、あまり必要性のない部分かな。

それよりも、現在の水分量%に対する閾値、30、40、60を調整できる方がよいと思いました。
また、チェック間隔、水やり時間も設定できるとよいと思いました。

コマンドの仕様②:

Watering :今すぐ水やり
W:30 :水やりレベル設定(初期値:30)
Y:40 :黄色レベル設定(初期値:40)
B:60 :青色レベル設定(初期値:60)
P:10 :ポンプ作動秒(初期値:10)
M:10 :待ち秒(初期値:10)

こんな感じでしょうか。
組んでみます。

class MyCallbacks: public BLECharacteristicCallbacks {
    void onWrite(BLECharacteristic *pCharacteristic) {
      std::string value = pCharacteristic->getValue();

      //作り込む
      if (value.length() > 0) {
        Serial.printf("Command:%s\n", value.c_str());
/*
        Serial.println("*********");
        Serial.print("New value: ");
        for (int i = 0; i < value.length(); i++)
          Serial.print(value[i]);

        Serial.println();
        Serial.println("*********");
*/
      }
    }
};

を作り込んでいくのですが、いざコマンド解釈を作ろうとしたとき、フォーマットが決まっていた方が作りやすいことに気が付きます。

コマンドの仕様③:

WN :今すぐ水やり
WL30 :水やりレベル設定(初期値:30)
YL40 :黄色レベル設定(初期値:40)
BL60 :青色レベル設定(初期値:60)
PT10 :ポンプ作動秒(初期値:10)
WT10 :待ち秒(初期値:10)

こうすると、最初の2文字が命令。3文字目以降があれば、数字となります。

色々気に入らない所があるのですが、初志貫徹。

とにかくサンプルをつなぎ合わせて、動作するところまで組み上げました。

今回、単体で動作するSALZmini2022に対し、その動作状況をタブレットやスマホで覗き見るレシーバーができました。さらに、コマンドを打ち込んで、動作させる機能も実装することができました。
これは、大きな成果です。

今後の展望

値を覗いてみると、水分計の値が思ったほど、高くないことが分かりました。これはおそらく、用土を朝明砂のみにしているためだと思われます。
今回作成した「WL」コマンドを使えば、水やりの閾値を変更することができるので、その最適値を試行錯誤したいと思っています。

また、今回作製したものは動作確認のためのとりあえず版です。BLEのライブラリを適当に呼んでそれなりの成果を出していますが、もう少しソースをじっくりと読み、整理をしていきたいと思います。

レシーバー側の作製についても、見た目をもう少し工夫し、オリジナルなものに変えていきたいと思っています。

そのためには、JavaScript、HTML/CSSをもう少し勉強する必要があります。今度こそは、コールバック地獄に陥らないよう気を引き締めて取り組みたいと思います。

あと、今回作ったレシーバーをネット上で動くようにしたいと思っています。無料のサービスを用いて公開することが可能であると考えています。このあたりの知識はほとんどなかったのでとてもよい研修課題となりました。

さいごに

最後までお読みいただき誠にありがとうございます。当初考えていた以上の成果が上がっていると思います。

ひとえに、Web Bluetooth APIのラッピングライブラリBlueJellyのおかげだと思っています。

大変参考になりました。ありがとうございます。

#自動潅水装置 #盆栽 #園芸 #電子工作 #M5StampC3 #WateringUnit #ArduinoIDE #BLE #WebBluetoothAPI #BlueJelly #Chromebook #IoT #Javascript


この記事が気に入ったらサポートをしてみませんか?