【SALZmini2022作製日誌】模索は続くよどこまでも
はじめに
前回記事
こちらの続きを行います。
・リクエストに応じて時系列データを送信する方法
・BOWLからリクエストをし、時系列データを受信、グラフ化する方法
が残っていました。
リクエストに応じて時系列データを送信する方法
前回はここで行き詰まりました。
そもそもBLEでストリームのようなデータを通信させるにはどうすればいいでしょうか。
PCとスマホを接続してファイル転送をしたり、ワイヤレスイヤフォンを接続して音声データをストリーミングしたりと、ごく当たり前にやっているはずのことですが、なかなか情報にヒットしません。
前回は、新しい特性(Characteristic)を増やすことで、対応できるのではなかろうかと考えたわけです。
#define SERVICE_UUID "fd531f24-3c63-482a-bfb1-2c493e6f2c07"
#define CHARACTERISTIC1_UUID "e953e123-8e3c-4de9-a70a-264898b4ea90"
#define CHARACTERISTIC2_UUID "339f5c93-f84b-4783-aa41-1ebde22d0ed3"
BLEServer *pServer = NULL;
BLEService *pService = NULL;
BLECharacteristic *pCharacteristic1 = NULL;
BLECharacteristic *pCharacteristic2 = NULL;
BLEAdvertising *pAdvertising = NULL;
// Create the service
pService = pServer->createService(SERVICE_UUID);
// Create the characteristic 1
pCharacteristic1 = pService->createCharacteristic(
CHARACTERISTIC1_UUID,
BLECharacteristic::PROPERTY_READ |
BLECharacteristic::PROPERTY_WRITE |
BLECharacteristic::PROPERTY_NOTIFY |
BLECharacteristic::PROPERTY_INDICATE
);
pCharacteristic1->setCallbacks(new MyCallbacks1());
// https://www.bluetooth.com/specifications/gatt/viewer?attributeXmlFile=org.bluetooth.descriptor.gatt.client_characteristic_configuration.xml
// Create a BLE Descriptor
pCharacteristic1->addDescriptor(new BLE2902());
// Set the characteristic value
//pCharacteristic1->setValue("Hello World");
// Create the characteristic 2
pCharacteristic2 = pService->createCharacteristic(
CHARACTERISTIC2_UUID,
BLECharacteristic::PROPERTY_NOTIFY
);
// Start the service
pService->start();
こんな感じです。
履歴データを扱う部分は円環バッファを実装。
こんな感じにしています。
typedef struct {
unsigned long millis; //4
uint8_t waterLevel; //1
} HistoryData;
#define MAX_HD 512
HistoryData historyData[MAX_HD];
int lHd = 0;
int tHd = 0;
int bHd = 0;
void
SetHistoryData(void)
{
if (++bHd > MAX_HD - 1)
bHd = 0;
if (++lHd > MAX_HD - 1) {
lHd = MAX_HD;
if (++tHd > MAX_HD - 1) {
tHd = 0;
}
}
historyData[bHd].millis = millis();
historyData[bHd].waterLevel = salzData.currentWaterLevel;
Serial.printf("SetHistoryData[l:%d t:%d b:%d](%ld, %d)",
lHd, tHd, bHd,
historyData[bHd].millis,
historyData[bHd].waterLevel
);
}
void
HistoryDataSend(void)
{
Serial.println("HistoryDataSend():");
//履歴データ送信
for (int i = 0; i < lHd; i++) {
//histryData[i].millis = 0L;
//histryData[i].waterLevel = 50;
pCharacteristic2->setValue((uint8_t*)&historyData[(i + tHd) % MAX_HD], sizeof(HistoryData));
pCharacteristic2->notify();
Serial.printf("historyData[i:%d](%ld, %d)",
i,
historyData[(i + tHd) % MAX_HD].millis,
historyData[(i + tHd) % MAX_HD].waterLevel
);
delay(10);
}
}
ステートマシン表も修正しました。
ところが、BLEレシーバーを作ろうとして気づいたのです。
//UUIDの設定
ble.setUUID("UUID1", "fd531f24-3c63-482a-bfb1-2c493e6f2c07",
"e953e123-8e3c-4de9-a70a-264898b4ea90");
2つ目の特性(Characteristic)とどうやって通信するのかということを。
今までの経験でBLE関係のプログラミングにおいて、自分の思い込みで進むと大ケガする可能性が高いと感じています。
思った通りにならず、泥沼化していく事態が容易に想像できます。
もう一度、課題を整理します。
やりたいことは、「リクエストに応じて時系列データを送信する方法」を確立することです。
今持っている手段を工夫して課題を解決する方が良いと考えました。
現在、SALZmini2022ではアドバタイズ信号とNotify信号が同じです。
そもそもそうする必要は全くなく、Notify信号で送信するデータに履歴データを混ぜ込むとよいのではないかと思いました。
ステートマシン表を元に戻し、再構築していきました。
Notifyデータを次のように設計しました。
typedef struct {
SALZData sd;
uint8_t iHd;
uint8_t nHd;
HistoryData hd;
} SALZNotifyData;
そして、データをこんな感じで発信します。
static SALZNotifyData pSnd;
void
NotifyData(void)
{
SALZNotifyData snd;
snd.sd = salzData;
snd.nHd = lHd;
if (snd.nHd > 0) {
snd.iHd = (pSnd.iHd + 1) % lHd;
snd.hd = historyData[(snd.iHd + tHd) % MAX_HD];
}
else {
snd.iHd = -1;
snd.hd.millis = 0L;
snd.hd.waterLevel = 0;
}
pCharacteristic->setValue((uint8_t*)&snd, sizeof(SALZNotifyData));
pCharacteristic->notify();
pSnd = snd;
}
後でまとめていて気が付いたのですが、Advertize信号を発信するときは、
void
SetAdvartiseData(void) {
// AdvartiseDataを設定する。送信は一定時間ごとに行われる。
Serial.printf("\nSetData():wl:%d st:%d c:%d wC:%d\n",
salzData.currentWaterLevel,
salzData.status,
salzData.count,
salzData.wCount);
BLEAdvertisementData oAdvertisementData = BLEAdvertisementData();
oAdvertisementData.setName("SM2022");
oAdvertisementData.setFlags(0x06); // BR_EDR_NOT_SUPPORTED | LE General Discoverable Mode
std::string strServiceData = "";
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)salzData.currentWaterLevel; // 水分量%
strServiceData += (char)salzData.status; // 状態
strServiceData += (char)(salzData.count & 0xff); // カウントの下位バイト
strServiceData += (char)((salzData.count >> 8) & 0xff); // カウントの上位バイト
strServiceData += (char)(salzData.wCount & 0xff); // 水やりカウントの下位バイト
strServiceData += (char)((salzData.wCount >> 8) & 0xff); // 水やりカウントの上位バイト
strServiceData = (char)strServiceData.length() + strServiceData;
oAdvertisementData.addData(strServiceData);
pAdvertising->setAdvertisementData(oAdvertisementData);
salzData.count = (++salzData.count < ULONG_MAX) ? salzData.count : 0;
}
こんな感じでデータを分解し再構築して発信していました。
Notifyデータを発信するときは、何も考えず、構造体のまま送信していました。
ble.onRead = function (data, uuid) {
console.log("> ble.onRead");
//フォーマットに従って値を取得
count = data.getUint16(0, true);
wCount = data.getUint16(2, true);
currentWaterLevel = data.getUint8(4);
status = data.getUint8(5);
wateringLevel = data.getUint8(6);
yellowLevel = data.getUint8(7);
blueLevel = data.getUint8(8);
pumpTime = data.getUint8(9);
waitTime = data.getUint8(10);
//履歴データを取得
iHd = data.getUint8(12); // アライメントがあるようだ。
nHd = data.getUint8(13);
millisHd = data.getUint32(14);
waterLevelHd = data.getUint8(20); // 理由がわからないがこれで読めた。
現在の受信の様子。
HistoryData構造体のメンバに、millisを含んでいます。実はこれ、unsigned long型なので、4バイト=32ビット必要です。
今回初めて扱います。
実はこの「byteOffset」の値の調整にてこずりました。
途中で何度もくじけそうになり、あきらめたくなりました。
一つのシステムの中でプログラムを組んでいる間は設計と実装の違いについてはあまり気にせず設計に集中していればいいのですが、2つのシステム間でデータのやり取りを行う場合、実装の状態を確認しながら行う必要があります。
以前のAdvertise信号を送受信した場合はサンプルを元に組みました。
こちらのサンプルは、このあたりの問題をあらかじめ回避するため、送信側でデータを再構成していたのです。
Notify信号はうっかりAruduinoIDEで構造体をそのまま流していたため、バイトアライメントや、エンディアンの調整が必要となったようです。
その結果、Notify信号発信ごとに、SALZmini2022本体に記憶された履歴データをサイクリックに送信できるようにしました。
ここまで準備しておけば次に進める。そう思っていました。
次の壁がすぐに見つかりました。しかも2つあります。
一つ目は、BOWLでデータを受信した後の処理です。
当初は、smoothie.jsというライブラリを使用したいと考えていました。いざ使おうと試してみると、オフラインで動かないのです。
私のスキルが足りないだけかもしれません。BLEを使うためのbluejelly.jsはすんなりオフラインでも動いたので、軽く考えていましたが、今のところ動いていません。
他のライブラリを試す。またはグラフだけなら、はじめから組んでも大したことないかもしれません。
二つ目は、履歴データに含んだmillisの扱いです。
当初のもくろみは、時計を持たないSALZmini2022の履歴データ管理として、装置の起動開始からのmillisを記録しておき、現在のmillisからの差を取ることで、どれぐらい過去のデータであるかわかります。ということは、現在のmillisを一緒にNotify信号にのせてあげないと駄目なんじゃないか?と思ったのです。
構造体にメンバを追加することはすぐできそうですが、気になることがあるのです。BLEのデータパケットでユーザーが自由にデザインできるデータのサイズが30バイト弱だったのではないか?と思っています。きちんと追いかけていないので確証はないです。ですが、現時点で20バイトを超えているので、少し気をつけなければいけません。
というわけで、少し進展はあったものの装置完成まではまだまだという状況です。
装置側も少し変えています。
実はSALZmini2022が完成間近と思いM5StampC3を買い増していました。このため実験装置を増やすことができました。
こんな感じです。
この時期、植え替えは良くないので、先日関西で開催された盆栽愛好者イベント「盆クラ展」で入手した、黄金黒松の苗木をそのままセットすることにしました。上蓋と鉢は針金で固定しました。
赤玉土に植え付けられているので、鉢の乾きが土の色で判断できます。
さいごに
今回も完成はしませんでしたが、次への進展がありました。また今回このように記事をまとめているのは、更に次のテーマを見つけてしまったためで、今までの状況をまとめたうえで、先に進もうと思っています。
今回も最後までお読み下さり誠にありがとうございました。
#自動潅水装置 #盆栽 #園芸 #電子工作 #BLE #ステートマシン図 #ArduinoIDE #M5StampC3 #SALZmini2022 #夏の自由研究
この記事が気に入ったらサポートをしてみませんか?