M5AtomLiteでSwitchBot温湿度計のデータをMQTTに投げるプログラム

新たにSwitchBot防水温湿度計を買ったので、またプログラムの変更が必要になりました。SwitchBotのデバイスは、以下のサイトの情報を元に、ServiceDataの最初の1Byteでデバイスの種類が判別できます。ただし、防水温湿度計の情報は無かったため、同様に調べたところ、0x77であることが分かりました。

https://github.com/OpenWonderLabs/SwitchBotAPI-BLE/blob/latest/README.md

|Product|	Device Type|Product|	Device Type
|--------|---------|--------|---------|
|Bot|(0x48)|温湿度計 |(0x54)
Humidifier|(0x65)|Curtain|(0x63)
Motion Sensor|(0x73)|Contact Sensor|(0x64)
Color Bulb|(0x75)|LED Strip Light|(0x72)
Smart Lock|(0x6F)|Plug Mini|(0x67)
温湿度計 Plus|(0x69)|防水温湿度計|(0x77)

この情報を元に、プログラムの修正を行いました。i2cにセンサを直結した場合にも対応させているため、ソースがやたらと長くなっていますが、BLE関連クラスの中が重要な部分です。

```M5stack:main.cpp
//
// i2c直結センサ対応（ENV2,ENV3,eCO2）
// MQTTパスワードあり,暗号化なし
//

#include <M5Atom.h>
#include <BLEDevice.h>
#include <BLEUtils.h>
#include <BLEScan.h>
#include <BLEAdvertisedDevice.h>
#include <WiFi.h>
#include <WiFiClientSecure.h>
#include <PubSubClient.h>
#include "Adafruit_Sensor.h"
#include <Adafruit_BMP280.h>
#include "M5_ENV.h"
#include "Adafruit_SGP30.h"

SHT3X sht30;
QMP6988 qmp6988;
Adafruit_SGP30 sgp;
Adafruit_BMP280 bmp;

char telNo[10] ="test";
char ClientID[20];
char COCOAdevName[20];
char ENVdevName[20];

// WiFiClientSecure https_client;
WiFiClient https_client;

PubSubClient mqttClient(https_client);
BLEScan* pBLEScan;

extern int ssid_len;
extern char ssid_list[2][20];
extern char password_list[2][20];
extern char server[20];
extern int port;
extern char mqttuser[20];
extern char mqttpasswd[20];
extern char root_ca[];


int scanTime = 5; //In seconds
int seq[32];
int maxCh[32];  //デバイス内の最小チャンネル
char MAC_Addr[48];
char dbuf[32][255];

//------------------------------------------------------------------
// 
//------------------------------------------------------------------
char uuid[37] = "0000fd6f-0000-1000-8000-00805f9b34fb";
// bool found = false;
bool CocoaFlag = false;
bool sgpFlag = false;
bool env3Flag = false;
bool env2Flag = false;
int deviceNum = 0;
int nearDeviceNum = 0;
int LastUpdate = 0;
char devices[64][17];
char TargetDev[17];



struct dev_info {
  char mac[20];       // Mac Address
  long last_update;   // Last Update
};

struct dev_info DEVs[32];
void mqttPublish(const char* topicBuffer,const char* buf);


//------------------------------------------------------------------------------------
//  BLE関連クラス
//------------------------------------------------------------------------------------

class MyAdvertisedDeviceCallbacks: public BLEAdvertisedDeviceCallbacks {
  void onResult(BLEAdvertisedDevice advertisedDevice) {
    if(!advertisedDevice.haveServiceData()) return;
    if(advertisedDevice.haveServiceUUID()){
      if (CocoaFlag && (strncmp(advertisedDevice.getServiceUUID().toString().c_str(),uuid, 36) == 0)){
          strncpy(TargetDev,advertisedDevice.getAddress().toString().c_str(),17);
          for (int i =0;i<deviceNum;i++){
            if(strncmp(devices[i],TargetDev,17)==0) return;
          }
          strncpy(devices[deviceNum],TargetDev,17); 
          int rssi = advertisedDevice.getRSSI();
          deviceNum++;
          if(rssi > -69) nearDeviceNum++;
      }
    }

    //---------------------------------------------------------------------------------------------
    //  Switch bot（）温湿度計のデータ読み込み
    //---------------------------------------------------------------------------------------------
    BLEUUID serviceUUID = advertisedDevice.getServiceDataUUID();

    if(
      (serviceUUID.equals(BLEUUID("00000d00-0000-1000-8000-00805f9b34fb")))||
      (serviceUUID.equals(BLEUUID("0000fd3d-0000-1000-8000-00805f9b34fb")))
     ){
      std::string s    = advertisedDevice.getServiceData();
      std::string md   = advertisedDevice.getManufacturerData();
      const char* uuid = serviceUUID.toString().c_str();
      const char* madata = md.c_str();
      const char* sedata = s.c_str();
      const char* MAC_Addr = advertisedDevice.getAddress().toString().c_str();

      if (strlen(MAC_Addr)==17){
        int i=0;
        char topic_buf[50];
        char buf[1000]; 
        for(i=0;((strlen(DEVs[i].mac)>0)&&(i<32));i++){
          if(strcmp(MAC_Addr,DEVs[i].mac)==0){
            //printf("Index:%d,%s,timer:%5.2f,\n",i,DEVs[i].mac,(millis()-DEVs[i].last_update)/1000.);
            if (millis() - DEVs[i].last_update > 29000){

              // printf("%02x:%02x:%02x:%02x ",madata[0],madata[1],madata[2],madata[3]);
              // printf("%02x:%02x:%02x:%02x ",madata[4],madata[5],madata[6],madata[7]);
              // printf("%02x:%02x:%02x:%02x ",madata[8],madata[9],madata[10],madata[11]);
              // printf("%02x:%02x:%02x\n",madata[12],madata[13],madata[14]);

              int battery=100;
              bool isTemperatureAboveFreezing;
              float temperature;
              int humidity;
              //温湿度計 or 温湿度計plus
              if((sedata[0]==0x54)||(sedata[0]==0x69)){
                battery = sedata[2] & 0b01111111;
                isTemperatureAboveFreezing = sedata[4] & 0b10000000;
                temperature = ( sedata[3] & 0b00001111 ) / 10.0 + ( sedata[4] & 0b01111111 );
                if(!isTemperatureAboveFreezing){temperature = -temperature;}
                humidity = sedata[5] & 0b01111111;
              }
              // 防水温湿度計
              if (sedata[0]==0x77){
                humidity = madata[12] & 0b01111111;
                isTemperatureAboveFreezing = madata[11] & 0b10000000;
                temperature = (madata[10] & 0b00001111 ) / 10.0 + (madata[11] & 0b01111111 );
                if(!isTemperatureAboveFreezing){temperature = -temperature;}
              }              
              
              M5.dis.fillpix(0x0000f0);
              snprintf (topic_buf, sizeof(topic_buf),"titc_data3/%s_%.5s/%s",telNo,MAC_Addr,"ENV");
              snprintf(buf,sizeof(buf),"tmp,F,%.2f,hum,I,%d,batt,I,%d",temperature,humidity,battery);
              mqttPublish(topic_buf,buf);
              DEVs[i].last_update = millis();
              delay(50);
              M5.dis.fillpix(0x000000);
              delay(50);
            }
            return;
          }
        }
        sprintf(DEVs[i].mac, "%s",MAC_Addr);
        DEVs[i].last_update = millis();
        printf("insert New Device to %d!!\n",i);
      }
    }
  }
};


//------------------------------------------------------------------------------------
// WiFi接続関数
//------------------------------------------------------------------------------------
void WifiConnect(int Count){  
  int cnt=0;
  while(WiFi.status() != WL_CONNECTED) {
    cnt++;
    for(int i=0;(WiFi.status()!= WL_CONNECTED&&i<ssid_len);i++){
      Serial.printf("connect to %s.",ssid_list[i]);
      WiFi.begin(ssid_list[i], password_list[i]);
      for(int j=0;(WiFi.status()!= WL_CONNECTED)&&(j<20);j++){
          delay(500);Serial.print(".");
      }
      Serial.printf("\n"); 
    }
    if (cnt>=Count){ESP.restart();}
  }
}

//------------------------------------------------------------------------------------
//  初期化メソッド
//------------------------------------------------------------------------------------

void setup() {
  M5.begin(true, false, true);
  Wire.begin(26, 32);  // Initialize i2c_pin 26,32 @ Atom
  Serial.begin(115200);

  snprintf(ClientID,sizeof(ClientID),"Denpa%s",telNo);
  snprintf(COCOAdevName,sizeof(COCOAdevName),"COCOA%s",telNo);
  snprintf(ENVdevName,sizeof(ENVdevName),"ENV%s",telNo);
  M5.dis.fillpix(0xf00000);
  Serial.println("\nScanning...");
  WifiConnect(3);
  M5.dis.setBrightness(10);
  M5.dis.fillpix(0x00f0f0);
  Serial.printf("WiFi connected!!\nIP address: ");
  Serial.print(WiFi.localIP());
  Serial.printf("\n");
  delay(500);
  for(int i=0;i<32;i++){
    seq[i]=-1;  maxCh[i]=-1;
    sprintf(dbuf[i],"BLEDEV%d",i);
  } 

 // eCO2 Unit 接続フラグ確認
  if (sgp.begin()){
    sgpFlag = true;
    Serial.print("Found SGP30 serial #");
    Serial.print(sgp.serialnumber[0], HEX);
    Serial.print(sgp.serialnumber[1], HEX);
    Serial.println(sgp.serialnumber[2], HEX);
  }else{Serial.println("SGP30 Not Found..");}
 
 // Env Unit 接続フラグ確認
 if(qmp6988.init()){
    env3Flag = true;
    Serial.println("Found ENVIII device!");
  }else{
    if(bmp.begin(0x76)){  
      env2Flag = true;
      Serial.println("Found ENVII device!");
    }else{Serial.println("ENV Unit Not Found..");}
  }

  // ボタンを押すまで60秒スリープ設定
  for(int i=0;i<50;i++){
    if (M5.Btn.wasPressed()) break;
    M5.dis.setBrightness(10);
    if(i%10<5)  M5.dis.clear();//M5.dis.drawpix(0, 0x000000);
    else        M5.dis.fillpix(0x00f0f0);//M5.dis.drawpix(0, 0x00f0f0);
    delay(100);
    M5.update();
  }

  Serial.printf("MQTT connection start..\n");
  M5.dis.setBrightness(10);
  M5.dis.fillpix(0x00f0f0);
  // https_client.setCACert(root_ca); 
  mqttClient.setServer(server,port);
  // mqttClient.setBufferSize(1000);
  mqttClient.connect(ClientID,mqttuser,mqttpasswd);
  printf("MQTT connected!\n");

  delay(500);
  

  for(int i=0;i<15;i++) seq[i]=-1;

  BLEDevice::init("");
  pBLEScan = BLEDevice::getScan(); //create new scan
  pBLEScan->setAdvertisedDeviceCallbacks(new MyAdvertisedDeviceCallbacks(), true);
  pBLEScan->setActiveScan(true); //active scan uses more power, but get results faster
  pBLEScan->setInterval(100);
  pBLEScan->setWindow(99);  // less or equal setInterval value
  M5.dis.fillpix(0x000000);
  printf("BLE init finished!\n"); 
  LastUpdate = millis();
  mqttPublish("titc_data3","test_data");
  
}

//=====================================================================================
// Loop関数
//=====================================================================================
void loop() {
  char buf1[100];
  char buf2[100];
  char topicbuf[200];
  char buf[1000];
  int cnt = 0;  
  deviceNum = 0;
  nearDeviceNum = 0;  
  CocoaFlag = false;
  // if (sgp.begin()){sgpFlag = true;}
  // if(qmp6988.init()){env3Flag = true;} else{env3Flag = false;}
  // if(bmp.begin(0x76)){env2Flag = true;} else{env2Flag = false;}

  while(!mqttClient.connected()){
    if(WiFi.status() != WL_CONNECTED) WifiConnect(3);
    printf(".");
    mqttClient.connect(ClientID,mqttuser,mqttpasswd);
    delay(500);
  }
  

  mqttClient.loop();
  if (millis() - LastUpdate > 29000){
    LastUpdate = millis();
    CocoaFlag = true;
    BLEScanResults foundDevices = pBLEScan->start(scanTime, false);
    delay(200);
    snprintf(buf,sizeof(buf),"");
    snprintf(buf1,sizeof(buf1),"");
    snprintf(buf2,sizeof(buf2),"");

    if(env3Flag){
      sht30.get();
      delay(50);
      snprintf(topicbuf,sizeof(topicbuf),"%s/tmp",ENVdevName);
      snprintf(buf,sizeof(buf),"%2.2f",sht30.cTemp);
      mqttPublish(topicbuf,buf);
      snprintf(topicbuf,sizeof(topicbuf),"%s/hum",ENVdevName);
      snprintf(buf,sizeof(buf),"%2.2f",sht30.humidity);
      mqttPublish(topicbuf,buf);

      snprintf(buf1,sizeof(buf1),"tmp,F,%2.2f,hum,F,%2.2f,press,F,%.2f",sht30.cTemp,sht30.humidity,qmp6988.calcPressure()/100);
    }
    if(env2Flag){
      sht30.get();
      delay(50);
      snprintf(topicbuf,sizeof(topicbuf),"%s/tmp",ENVdevName);
      snprintf(buf,sizeof(buf),"%2.2f",sht30.cTemp);
      mqttPublish(topicbuf,buf);
      snprintf(topicbuf,sizeof(topicbuf),"%s/hum",ENVdevName);
      snprintf(buf,sizeof(buf),"%2.2f",sht30.humidity);
      mqttPublish(topicbuf,buf);


      snprintf(buf1,sizeof(buf1),"tmp,F,%2.2f,hum,F,%2.2f,press,F,%.2f",sht30.cTemp,sht30.humidity,bmp.readPressure()/100);
    }

    if (sgpFlag) {
      if (!sgp.IAQmeasure()){
        Serial.println("Measurement failed");
        return;
      }
      snprintf(topicbuf,sizeof(topicbuf),"%s/eCO2",ENVdevName);
      snprintf(buf,sizeof(buf),"%d",sgp.eCO2);
      mqttPublish(topicbuf,buf);
      snprintf(buf2,sizeof(buf2),",TVOC,I,%d,eCO2,I,%d",sgp.TVOC,sgp.eCO2);
    }


    if ((env2Flag||env3Flag)||(sgpFlag && sgp.IAQmeasure())){
      snprintf(topicbuf,sizeof(topicbuf),"%s/%s/%s","titc_data3",telNo,"ENV");
      snprintf(buf,sizeof(buf),"%s%s",buf1,buf2);
      mqttPublish(topicbuf,buf);
    }
    // snprintf(topicbuf,sizeof(topicbuf),"%s/%s/%s","titc_data3",telNo,"COCOA");
    // snprintf(buf,sizeof(buf),"cocoa,I,%d,near,I,%d",deviceNum,nearDeviceNum);
    // mqttPublish(topicbuf,buf);
  }
  pBLEScan->clearResults();   // delete results fromBLEScan buffer to release memory
  delay(200);
}

//-----------------------------------------------------------------------
//topicを含むpublisher
//-----------------------------------------------------------------------
void mqttPublish(const char* topicBuffer,const char* buf) {
  // while(WiFi.status() != WL_CONNECTED){
  //   printf(".");
  //   WifiConnect(3);
  // } 
  // while(!mqttClient.connected()){
  //   printf("mqtt reconnect.\n");
  //   mqttClient.connect(ClientID,mqttuser,mqttpasswd);
  //   delay(200);
  // } 
  mqttClient.publish(topicBuffer,buf);
  
  printf("topic:%s\tmsg:%s\n",topicBuffer,buf);
}
```