カワリモノ息子の技術メモ的な~

カワリモノ息子とその母の技術メモ的な〜

学校が苦手な息子くんの作品とその母の作品、はたまた日常などいろいろを在宅エンジニア母が綴る

M5Stackでオムロンの環境センサから受信したデータを表示した

息子くん母が最近ドはまりしているM5Stackのことを書こうと思います。
(今回は息子くんの作品ではありません。)

M5Stack FIRE

先日紹介しましたが小型でかわいらしいデバイスです。
siroitori.hatenablog.com
この記事の下の方に

今現在、自宅にあるオムロンの環境センサから温度や湿度などの値をBLEで受信するコーディングをして表示することもできました。

と書いていて絶賛開発中です。

ひととおり完成したらブログ書こうかなと思ったのですが、つまづいたところも多くあり、書くと長くなりそう(もう書きたくなくなりそうな予感も…)だったのでとりあえず
オムロンの環境センサからデータを受信する話だけを書くことにしました。

f:id:toriko0413:20200208223331j:plain

オムロンの環境センサ

以前(2年前)、ラズパイで開発をしました。
siroitori.hatenablog.com
この記事の「優良賞をいただいた作品の紹介」から下の部分です。

このときは、ラズパイで値を受信しており、pythonでコーディングしていました。
今回はM5StackなのでArduinoでコーディングしました。

C++は業務で昔少しだけかじったことはあるのですが、人のコードをちょっといじって何かの値を変えるくらいの修正をするレベルで、自分でプログラム書いたことは皆無です!
C言語といえばポインタとかね。なんとなくの概念しかありません…。

でもこんな私でもできた!なんでもチャレンジですね。

プログラム

まずは参考になりそうなサイトを検索

しかし検索してもなかなか情報が無く。
M5Stackから送信するプログラムはたくさんヒットするのですが受信するものがあまりない?

① M5Stack →→Bluetooth→→ どこか
(M5Stack のサーバー用途)はあるが、
② どこか →→Bluetooth→→ M5Stack
(M5Stack のクライアント用途)がない。

Twitterの親切な方からESP32やセントラル/ペリフェラルとか入れて検索ワードを変えるといいよとアドバイスいただきました!感謝です。

ESP32
 M5Stackは内部でESP32という名前のマイコンが入っています。これは今回いろいろ検索しながら知りました。
セントラル/ペリフェラル
 初耳でした。上記の①がペリフェラルで②がセントラル、になります。いいこと聞きました!

そしてネットの海から情報を検索します。
できれば、オムロンの環境センサからデータを受信するプログラムの情報が載ったサイトがあれば望ましいのですが…

M5StackでBLE環境センサー端末を作る – Ambient
これは逆方向ですね。(先にこれ見てたのにセントラル・プリフェラルって用語スルーしてることに後で気が付く)

あっ!ありました!やりたいのこれ。
qiita.com

そっくりそのまま動かしてみる

上記公開されているプログラムで、環境センサのMACアドレスを定義している部分

static String omronSensorAdress = "ff:f7:ef:33:f4:31";

だけ自分のものに書き換えて実行してみました。

すると・・・
あれれ、コネクトでうまくいかない。うーんうーん。
(本当はコネクトの前段階でもArduino初心者のためわからなくて試行錯誤してたんですが何につまづいていたのか忘れてしまいました…)

ブロードキャストモードの仕様

原因は、環境センサの設定でした。

オムロンの環境センサには、モードが2種類あります。
f:id:toriko0413:20200208213402j:plain
https://ambidata.io/samples/m5stack/m5stack_ble_sensor/ より引用)

初期時には「コネクトモード」になっているのですが、
以前ラズパイからpythonで取得していた時に「ブロードキャストモード」に変えてたんでした!そういえば!

この図からわかるように、ブロードキャストモードではコネクトする必要がありません。
アドバタイズ時に一気にセンサーのデータが取得できます!

上記プログラムのonResultの中の

Serial.println(advertisedDevice.toString().c_str());

では、ブロードキャストモードの時以下のような値が取得されます。

Name: , Address: fd:e5:99:72:2c:29, manufacturer data: d502d77207a514990002000628c80d9600affffe26a2

あ、そうそう、Serial.printの値もどこに出てくるのかわからず悩んでたんだった。
Arduinoの[ツール]→[シリアルモニタ]で画面を出すことを知りました。
息子くんに訪ねて一発解決^^;
ほんとそんなとこから!?って感じですよ。

そして話を戻して、この取得されたmanufacturer data: 以降の値に環境センサのデータが埋まってるんですねぇ!

データの仕様は以下
f:id:toriko0413:20200208214807j:plain

行番号以降のデータについては、「ブロードキャストモード」ではどうやら以下ではなく、
f:id:toriko0413:20200208214702p:plain

こちらを見る模様。
f:id:toriko0413:20200208220103p:plain

上記仕様にあてはめて、
d502d77207a514990002000628c80d9600affffe26a2
だと、下記のように分割して解釈し10進数に変換します。
d502 :カンパニーID(固定)
d7 :シーケンス番号 → [dec] 215
7207 :温度 → [dec] 1906 → 19.6℃
a514 :湿度 → [dec] 5285 → 52.85%
9900 :照度 → [dec] 153
0200 :UV → [dec] 2
0628 :気圧 → [dec] 10246
c80d :騒音 → [dec] 3528
9600 :加速度X → [dec] 150
afff :加速度Y → [dec] 65455
fe26 :加速度Z → [dec] 9982
a2 :電池 → [dec] 162

これは同じことを2年前にpythonで書いてたから、pythonのコードとマニュアルを読み返してネットも調べてなんとか思い出しました。
こうやって思い出せないからブログに書いとくの大事ですね^^;;

Arduinoで実装

慣れないArduinoのコード。
もっと良い書き方がありそうな気がしていますが…
そしてこれは定数にするべきでしょうっていう箇所も見られますが…
(私がコードレビューのレビュワーなら一番に指摘するでしょう)

とりあえず、恥を忍んで公開します。

#include "BLEDevice.h"
#include <M5Stack.h>

static String omronSensorAdress = "fd:e5:99:72:2c:29";
static BLEUUID serviceUUID("0c4c3000-7700-46f4-aa96-d5e974e32a54");
static BLEUUID    charUUID("0c4C3001-7700-46F4-AA96-D5E974E32A54");

static BLEAddress *pServerAddress;
static boolean doConnect = false;
static BLERemoteCharacteristic* pRemoteCharacteristic;

long getParamFromAdvertisedData(std::string advertisedDataString, int pos, int length, String paramName) {
  // 38桁の16進表記文字列から対象のパラメータ位置で抜き出して10進数に変換して戻す
  std::string substrData = "";
  String manufData = "";
  Serial.print(paramName);
  Serial.print(": [hex] ");
  M5.Lcd.print(paramName);
  substrData = advertisedDataString.substr(pos, length); // 指定位置から指定桁数抜き出す
  Serial.print(substrData.c_str());

  if (length > 2){
    // 2桁以上(今回は4桁しかない)のときは反対にする 例)7d08→087d
    for(int i=length-2; i>=0; i=i-2){
      manufData.concat(substrData.substr(i, 2).c_str());  
    }
  }else{
    manufData = substrData.c_str();
  }

  // 16進数→10進数変換
  long ret = strtol(manufData.c_str(), NULL, 16);
  Serial.print(" [dec] ");
  Serial.println(ret);
  M5.Lcd.print(": ");
  M5.Lcd.println(ret);
  
  return ret;
}

/**
 * Scan for BLE servers and find the first one that advertises the service we are looking for.
 */
class MyAdvertisedDeviceCallbacks: public BLEAdvertisedDeviceCallbacks {
 /**
   * Called for each advertising BLE server.
   */
  void onResult(BLEAdvertisedDevice advertisedDevice) {
    BLEScan* pBLEScan = BLEDevice::getScan();
    Serial.println("BLE Advertised Device found: ");
    std::string advertisedDataString = advertisedDevice.toString().c_str();
    Serial.println(advertisedDataString.c_str());  // このなかの文字列 manufacturer data:d502のあとがデータになる

    String deviceAddress = advertisedDevice.getAddress().toString().c_str();
    Serial.println(deviceAddress.c_str());
    //ペリフェラルをアドレスで判断
    if(deviceAddress.equalsIgnoreCase(omronSensorAdress)){

      int manufPos = advertisedDataString.find("manufacturer data: d502");
      Serial.print("manufPos: ");
      Serial.println(manufPos);

      M5.Lcd.fillScreen(WHITE);
      M5.Lcd.setCursor(0,0);

      long seq = getParamFromAdvertisedData(advertisedDataString, manufPos+23, 2, "seq");
      
      long temp = getParamFromAdvertisedData(advertisedDataString, manufPos+23+2, 4, "temp");
      long humid = getParamFromAdvertisedData(advertisedDataString, manufPos+23+2+4, 4, "humid");
      long light = getParamFromAdvertisedData(advertisedDataString, manufPos+23+2+4+4, 4, "light");
      long uv = getParamFromAdvertisedData(advertisedDataString, manufPos+23+2+4+4+4, 4, "uv");
      long press = getParamFromAdvertisedData(advertisedDataString, manufPos+23+2+4+4+4+4, 4, "press");
      long noise = getParamFromAdvertisedData(advertisedDataString, manufPos+23+2+4+4+4+4+4, 4, "noise");
      long accelX = getParamFromAdvertisedData(advertisedDataString, manufPos+23+2+4+4+4+4+4+4, 4, "accelX");
      long accelY = getParamFromAdvertisedData(advertisedDataString, manufPos+23+2+4+4+4+4+4+4+4, 4, "accelY");
      long accelZ = getParamFromAdvertisedData(advertisedDataString, manufPos+23+2+4+4+4+4+4+4+4+4, 4, "accelZ");
      long batt = getParamFromAdvertisedData(advertisedDataString, manufPos+23+2+4+4+4+4+4+4+4+4+4, 2, "batt");
      
     doConnect = true;
    }// Found our server
  } // onResult
}; // MyAdvertisedDeviceCallbacks

void setup() {
  Serial.begin(115200);
  M5.begin(true,false,true);
  M5.Lcd.fillScreen(WHITE);
  M5.Lcd.setTextSize(2);
  Serial.println("Step 0");
  Serial.println("Starting Arduino BLE Client application...");
  BLEDevice::init("");

} // End of setup.


// This is the Arduino main loop function.
void loop() {
  BLEScan* pBLEScan = BLEDevice::getScan();
  pBLEScan->setAdvertisedDeviceCallbacks(new MyAdvertisedDeviceCallbacks());
  pBLEScan->setActiveScan(true);
  pBLEScan->start(5);

  delay(1000); // Delay a second between loops.
} // End of loop


まとめ

まともにCやったことないハード系はちんぷんかんぷんな私でも頑張って実装することができました。
できあがると超うれしいですね。

今現在はこれをまだ改造していっているのですが、続きもおいおい書いていこうと思います。
それと並行してM5Stackでファービーを作ろうとしているので、それも書こうかなと思っています。

作りながらまだよくわからない部分もありながら書いているところもあり、よく勉強しないといけないなって思っています。

毎日かなり開発に意欲的な息子くんから刺激をうけてはじめたArduinoな世界です。
息子くんに感謝をするとともに、
息子には負けてられん!
という気持ちでこれからも頑張ります^^



スター・はてブとても嬉しいです