投稿者: sassy

  • IoT百葉箱を作る

    IoT百葉箱を作る

    温度センサー、湿度センサー、気圧センサーとMQTT送信器としてのESP-WROOM-02を収納するだけ
    なので昔小中学校にあったような大きなものは必要ない。
    収納ケースとしては、未来工業製の屋根付きウォルボックスにした。
    色はなるべくそれっぽいミルキーホワイトが良いかな。

    そのまま使用すると密閉ケースなので宜しくないので「ガラリ」を付けることにした。
    ガラリとは、通風や換気を行うためのブラインドのようなものです。
    これを付けることにより、少しでも外気温や外湿度に近づけたいと思います。

           未来工業 WB-1AM                   杉田エース 50型ガラリ     
         購入先 モノタロウ ¥1,758                 購入先 アマゾン¥429
                                   

    配線図

    シンプルな普通の回路で構成されている。
    CPUはESP-WROOM-02を使用している。

     AE-ESP-WROOM-02U-1100MIL 
    購入先 秋月電子 ¥600

    肝心の温度、湿度、気圧センサーだが、スイッチサイエンスの「SSCI-096942」というのを使用した。
    主な仕様は以下のとうり

    特長

    • 温湿度気圧センサBME280搭載
    • Grove互換コネクタとQwiic/STEMMA QTコネクタ搭載
    • 電源電圧3.3 V(Qwiic/STEMMA QTコネクタから給電時)
    • 電源電圧5 V(Grove互換コネクタから給電時※信号レベルは3.3 V)
    • I2Cアドレス:77h(SDOを100 K抵抗でプルアップ)

    BME280の仕様

    測定精度:±1 hPa
    動作電圧:1.71 V~ 3.6 V
    IO電圧:1.2 V~ 3.6 V
    消費電流:3.6 uA(温度・湿度・気圧の1 Hz測定)
    動作温度範囲:-40°C~+85°C
    気温
    測定範囲:-40~+85℃
    分解能:0.01℃
    測定精度:±0.5℃(25℃)
    湿度
    測定範囲:0~100 %
    分解能:0.008 %
    測定精度:±3 %
    気圧
    測定範囲:300~1100 hPa
    分解能:0.18 Pa

    購入先 スイッチサイエンス SSCI-096942 ¥1,980

    基板内配線図

    I2Cラインは、基板上で4.7kΩでプルアップ済なので何もしなくていい。
    CSBラインは、I2CとSPI通信機能を切り替える為のラインなので基板上で半田盛にて3.3vラインに接続
    SDO端子は、内部で100kΩで3.3vラインに接続されている。これにより、I2Cのアドレスは、77hになる
    コネクターが2つあるが、今回は使用しないで基板上のホールに直接配線した。

    スクリプト ESP-WROOM-02 向け

    スイッチサイエンスのサイトにサンプルスクリプトが掲載されているのでそのまま持ってくればOK。
    私の場合には、NORE-REDのMQTTサーバーに送信するのでその部分の処理を加えている。
    このセンサー搭載のESP-WROOM-02のIPアドレスは、固定した方が使いやすいので
    WiFi.config(IPAddress(192, 168, 1, 100), IPAddress(192, 168, 1, 1), IPAddress(255, 255, 255, 0));
    で直接アドレスを指定している。
    WiFiconfig(IPアドレス,デフォルトゲートウェイアドレス,サブネットマスク)
    自分の家の空いているIPアドレスを調べるには以下のソフト「Advanced IP Scanner」を使用すると
    便利。
    https://forest.watch.impress.co.jp/library/software/advipscanner/
    3.3v系のLED制限抵抗は、200Ω~程度だが、ここではループ動作しているかどうか確認が目的なので
    消費電流を抑える為あえて大き目の1kΩにしている。

    #include <ESP8266WiFi.h>
    #include <PubSubClient.h>
    #include <Wire.h>
    
    #define BME280_ADDRESS 0x77
    unsigned long int hum_raw,temp_raw,pres_raw;
    signed long int t_fine;
    
    uint16_t dig_T1;
    int16_t dig_T2;
    int16_t dig_T3;
    uint16_t dig_P1;
    int16_t dig_P2;
    int16_t dig_P3;
    int16_t dig_P4;
    int16_t dig_P5;
    int16_t dig_P6;
    int16_t dig_P7;
    int16_t dig_P8;
    int16_t dig_P9;
    int8_t  dig_H1;
    int16_t dig_H2;
    int8_t  dig_H3;
    int16_t dig_H4;
    int16_t dig_H5;
    int8_t  dig_H6;
    //アサインI2C SCL SDA ポート番号
    const int sclPin = 5;
    const int sdaPin = 4;
    const int LED = 16;
    const char* ssid = "your_ssid";
    const char* password = "your_passwd";
    const char* mqtt_server = "192.168.1.200";// your_MQTT Server Address
    long lastMsg = 0;
    float msg;
    
    WiFiClient espClient;
    PubSubClient client(espClient);
    
    void setup()
    { 
      pinMode(LED, OUTPUT); 
      digitalWrite(LED, LOW); 
      Serial.begin(9600);
      Wire.begin(sdaPin, sclPin);
      setup_wifi();
      client.setServer(mqtt_server, 1883);
      WiFi.config(IPAddress(192, 168, 1, 100), IPAddress(192, 168, 1, 1), IPAddress(255, 255, 255, 0));//IP Address 
      uint8_t osrs_t = 1;             //Temperature oversampling x 1
      uint8_t osrs_p = 1;             //Pressure oversampling x 1
      uint8_t osrs_h = 1;             //Humidity oversampling x 1
      uint8_t mode = 3;               //Normal mode
      uint8_t t_sb = 5;               //Tstandby 1000ms
      uint8_t filter = 0;             //Filter off 
      uint8_t spi3w_en = 0;           //3-wire SPI Disable
        
      uint8_t ctrl_meas_reg = (osrs_t << 5) | (osrs_p << 2) | mode;
      uint8_t config_reg    = (t_sb << 5) | (filter << 2) | spi3w_en;
      uint8_t ctrl_hum_reg  = osrs_h;
        
      Serial.begin(9600);
      Wire.begin();
        
      writeReg(0xF2,ctrl_hum_reg);
      writeReg(0xF4,ctrl_meas_reg);
      writeReg(0xF5,config_reg);
      readTrim();   
    }
    void setup_wifi() {
      delay(10);
      // We start by connecting to a WiFi network
      WiFi.begin(ssid, password);
      while (WiFi.status() != WL_CONNECTED) {
      delay(500);
      }
    }
    void reconnect() {
      // Loop until we're reconnected
      while (!client.connected()) {
      // Create a random client ID
      String clientId = "ESP8266Client-";
      clientId += String(random(0xffff), HEX);
      // Attempt to connect
      if (client.connect(clientId.c_str())) {
      client.subscribe("inTopic");
      } else {
      // Wait 0.5 seconds before retrying
      delay(500);
        }
      }
    }
    
    void readTrim()
    {
      uint8_t data[32],i=0;                      // Fix 2014/04/06
      Wire.beginTransmission(BME280_ADDRESS);
      Wire.write(0x88);
      Wire.endTransmission();
      Wire.requestFrom(BME280_ADDRESS,24);       // Fix 2014/04/06
      while(Wire.available()){
            data[i] = Wire.read();
            i++;
      }
        
      Wire.beginTransmission(BME280_ADDRESS);    // Add 2014/04/06
      Wire.write(0xA1);                          // Add 2014/04/06
      Wire.endTransmission();                    // Add 2014/04/06
      Wire.requestFrom(BME280_ADDRESS,1);        // Add 2014/04/06
      data[i] = Wire.read();                     // Add 2014/04/06
      i++;                                       // Add 2014/04/06
        
      Wire.beginTransmission(BME280_ADDRESS);
      Wire.write(0xE1);
      Wire.endTransmission();
      Wire.requestFrom(BME280_ADDRESS,7);        // Fix 2014/04/06
      while(Wire.available()){
            data[i] = Wire.read();
            i++;    
      }
      dig_T1 = (data[1] << 8) | data[0];
      dig_T2 = (data[3] << 8) | data[2];
      dig_T3 = (data[5] << 8) | data[4];
      dig_P1 = (data[7] << 8) | data[6];
      dig_P2 = (data[9] << 8) | data[8];
      dig_P3 = (data[11]<< 8) | data[10];
      dig_P4 = (data[13]<< 8) | data[12];
      dig_P5 = (data[15]<< 8) | data[14];
      dig_P6 = (data[17]<< 8) | data[16];
      dig_P7 = (data[19]<< 8) | data[18];
      dig_P8 = (data[21]<< 8) | data[20];
      dig_P9 = (data[23]<< 8) | data[22];
      dig_H1 = data[24];
      dig_H2 = (data[26]<< 8) | data[25];
      dig_H3 = data[27];
      dig_H4 = (data[28]<< 4) | (0x0F & data[29]);
      dig_H5 = (data[30] << 4) | ((data[29] >> 4) & 0x0F); // Fix 2014/04/06
      dig_H6 = data[31];                                   // Fix 2014/04/06
    }
    
    void writeReg(uint8_t reg_address, uint8_t data)
    {
      Wire.beginTransmission(BME280_ADDRESS);
      Wire.write(reg_address);
      Wire.write(data);
      Wire.endTransmission();    
    }
    
    
    void readData()
    {
      int i = 0;
      uint32_t data[8];
      Wire.beginTransmission(BME280_ADDRESS);
      Wire.write(0xF7);
      Wire.endTransmission();
      Wire.requestFrom(BME280_ADDRESS,8);
      while(Wire.available()){
            data[i] = Wire.read();
            i++;
      }
      pres_raw = (data[0] << 12) | (data[1] << 4) | (data[2] >> 4);
      temp_raw = (data[3] << 12) | (data[4] << 4) | (data[5] >> 4);
      hum_raw  = (data[6] << 8) | data[7];
    }
    
    signed long int calibration_T(signed long int adc_T)
    {    
      signed long int var1, var2, T;
      var1 = ((((adc_T >> 3) - ((signed long int)dig_T1<<1))) * ((signed long int)dig_T2)) >> 11;
      var2 = (((((adc_T >> 4) - ((signed long int)dig_T1)) * ((adc_T>>4) - ((signed long int)dig_T1))) >> 12) * ((signed long int)dig_T3)) >> 14;
        
      t_fine = var1 + var2;
      T = (t_fine * 5 + 128) >> 8;
      return T; 
    }
    
    unsigned long int calibration_P(signed long int adc_P)
    {
      signed long int var1, var2;
      unsigned long int P;
      var1 = (((signed long int)t_fine)>>1) - (signed long int)64000;
      var2 = (((var1>>2) * (var1>>2)) >> 11) * ((signed long int)dig_P6);
      var2 = var2 + ((var1*((signed long int)dig_P5))<<1);
      var2 = (var2>>2)+(((signed long int)dig_P4)<<16);
      var1 = (((dig_P3 * (((var1>>2)*(var1>>2)) >> 13)) >>3) + ((((signed long int)dig_P2) * var1)>>1))>>18;
      var1 = ((((32768+var1))*((signed long int)dig_P1))>>15);
      if (var1 == 0)
      {
            return 0;
      }    
      P = (((unsigned long int)(((signed long int)1048576)-adc_P)-(var2>>12)))*3125;
      if(P<0x80000000)
      {
       P = (P << 1) / ((unsigned long int) var1);   
      }
      else
      {
        P = (P / (unsigned long int)var1) * 2;    
      }
      var1 = (((signed long int)dig_P9) * ((signed long int)(((P>>3) * (P>>3))>>13)))>>12;
      var2 = (((signed long int)(P>>2)) * ((signed long int)dig_P8))>>13;
      P = (unsigned long int)((signed long int)P + ((var1 + var2 + dig_P7) >> 4));
      return P;
    }
    
    unsigned long int calibration_H(signed long int adc_H)
    {
      signed long int v_x1;
        
      v_x1 = (t_fine - ((signed long int)76800));
      v_x1 = (((((adc_H << 14) -(((signed long int)dig_H4) << 20) - (((signed long int)dig_H5) * v_x1)) + 
                  ((signed long int)16384)) >> 15) * (((((((v_x1 * ((signed long int)dig_H6)) >> 10) * 
                  (((v_x1 * ((signed long int)dig_H3)) >> 11) + ((signed long int) 32768))) >> 10) + (( signed long int)2097152)) * 
                  ((signed long int) dig_H2) + 8192) >> 14));
      v_x1 = (v_x1 - (((((v_x1 >> 15) * (v_x1 >> 15)) >> 7) * ((signed long int)dig_H1)) >> 4));
      v_x1 = (v_x1 < 0 ? 0 : v_x1);
      v_x1 = (v_x1 > 419430400 ? 419430400 : v_x1);
      return (unsigned long int)(v_x1 >> 12);   
    }
    
    
    
    void loop()
    {
      digitalWrite(LED,HIGH);
      delay(500);
    
      if (!client.connected()) {
        reconnect();
      }
      client.loop();
     
      double temp_act = 0.0, press_act = 0.0,hum_act=0.0;
      signed long int temp_cal;
      unsigned long int press_cal,hum_cal;
        
      readData();
        
      temp_cal = calibration_T(temp_raw);
      press_cal = calibration_P(pres_raw);
      hum_cal = calibration_H(hum_raw);
      temp_act = (double)temp_cal / 100.0;
      press_act = (double)press_cal / 100.0;
      hum_act = (double)hum_cal / 1024.0;
    
    
      char buf0[12]="";
      char buf1[12]="";
      char buf2[12]="";
      char buf3[12]="";
    
      dtostrf(temp_act, 2, 1, buf0);
      dtostrf(hum_act, 3, 1, buf1);
      dtostrf(press_act, 4, 0, buf2);
    
      delay(50);
      
      client.publish("temperature",buf0);
      delay(50);
      client.publish("humidity",buf1);
      delay(50);
      client.publish("pressure",buf2);
    
      digitalWrite(LED,LOW);
      delay(500);
    }

    IoT百葉箱完成品

    NODE-RED
    事前に以下のページを参考にRaspberry PiにNODE-REDさらにMQTTサーバーをインストールして下さい。
    https://marginalvillage.com/page/4/

    NODE-REDで右上にある三のアイコンをクリックして「パレットの管理」を開き
    「ノードを追加」で「@flexdash/node-red-fd-corewidgets」を追加して下さい。
    ちょっと前までは通常「node-red-dashboard」を使っていましたが、最近不具合が発生しております。
    具体的には、ダッシュボードが開かなる現象です。
    正常動作する場合もあるので厄介です。
    現在「非推奨」マークが付いているのでインストールしている場合にはアンインストールして下さい、同時に2つは干渉しますので注意して下さい。

    説明
    AM6:00~PM21:00迄1時間おきに外温度・湿度・気圧をGoogle HOMEへ送信して音声で報告します。
    injectで文字の”1”を書き出すと日本語で、”2″を書き出すと英語で音声が出ます。
    現在は”1″(日本語)の設定をしてあります。
    必要なら変更して下さい。
    1時間後にセンサーデーターが更新されていない場合には、Google HOMEから音声で「屋外の
    温度系気象センサーが機能していません、点検して下さい。」と報告されます。
    以下のリストをNODE-REDのメニュー「読み込み」からコピペすれば使用できます。
    MQTTサーバーアドレス、Google HOME IPアドレスは自分のものに変更して下さい。

    [{"id":"97c4136d2899d345","type":"tab","label":"フロー 1","disabled":false,"info":"","env":[]},{"id":"71705a05fce304b9","type":"mqtt in","z":"97c4136d2899d345","name":"","topic":"temperature","qos":"2","datatype":"auto-detect","broker":"a4ab6ee7518f1403","nl":false,"rap":true,"rh":0,"inputs":0,"x":130,"y":160,"wires":[["500e5bb504590a2d","3691fa41dcd9f286","f64296de16af15f5","64a1758de5a10549"]]},{"id":"500e5bb504590a2d","type":"debug","z":"97c4136d2899d345","name":"debug 1","active":false,"tosidebar":true,"console":false,"tostatus":false,"complete":"false","statusVal":"","statusType":"auto","x":340,"y":60,"wires":[]},{"id":"3691fa41dcd9f286","type":"function","z":"97c4136d2899d345","name":"書込み","func":"var data = String(msg.payload);\nglobal.set(\"out_temperature\", data);\nglobal.set(\"temperature_update_flg\", \"1\");\nreturn msg;","outputs":1,"timeout":0,"noerr":0,"initialize":"","finalize":"","libs":[],"x":330,"y":120,"wires":[[]]},{"id":"f64296de16af15f5","type":"ui-text","z":"97c4136d2899d345","group":"bf0acc3b3c4329c9","order":1,"width":0,"height":0,"name":"屋外温度","label":"屋外温度 ℃","format":"{{msg.payload}}","layout":"col-center","style":false,"font":"","fontSize":16,"color":"#717171","wrapText":false,"className":"","value":"payload","valueType":"msg","x":340,"y":160,"wires":[]},{"id":"64a1758de5a10549","type":"ui-chart","z":"97c4136d2899d345","group":"bf0acc3b3c4329c9","name":"グラフ","label":"","order":2,"chartType":"line","category":"topic","categoryType":"msg","xAxisLabel":"","xAxisProperty":"","xAxisPropertyType":"timestamp","xAxisType":"time","xAxisFormat":"","xAxisFormatType":"{HH}:{mm}","xmin":"","xmax":"","yAxisLabel":"","yAxisProperty":"payload","yAxisPropertyType":"msg","ymin":"-5","ymax":"40","bins":10,"action":"append","stackSeries":false,"pointShape":"dash","pointRadius":"0","showLegend":false,"removeOlder":1,"removeOlderUnit":"86400","removeOlderPoints":"","colors":["#0095ff","#ff0000","#ff7f0e","#2ca02c","#a347e1","#d62728","#ff9896","#9467bd","#c5b0d5"],"textColor":["#666666"],"textColorDefault":true,"gridColor":["#404040"],"gridColorDefault":false,"width":"6","height":"3","className":"","interpolation":"linear","x":330,"y":200,"wires":[[]]},{"id":"333d9fc2c517b19c","type":"mqtt in","z":"97c4136d2899d345","name":"","topic":"humidity","qos":"2","datatype":"auto-detect","broker":"a4ab6ee7518f1403","nl":false,"rap":true,"rh":0,"inputs":0,"x":120,"y":400,"wires":[["fd9b944cb53abaa2","3c702113cdebf002","bb2df56d67c5f829","81d965e31a8a2e69"]]},{"id":"81d965e31a8a2e69","type":"debug","z":"97c4136d2899d345","name":"debug 2","active":false,"tosidebar":true,"console":false,"tostatus":false,"complete":"false","statusVal":"","statusType":"auto","x":340,"y":300,"wires":[]},{"id":"bb2df56d67c5f829","type":"function","z":"97c4136d2899d345","name":"書込み","func":"global.set(\"out_humidity\", msg.payload);\nglobal.set(\"humidity_update_flg\", \"1\");\nreturn msg;","outputs":1,"timeout":0,"noerr":0,"initialize":"","finalize":"","libs":[],"x":330,"y":360,"wires":[[]]},{"id":"fd9b944cb53abaa2","type":"ui-text","z":"97c4136d2899d345","group":"bf0acc3b3c4329c9","order":3,"width":0,"height":0,"name":"屋外湿度","label":"屋外湿度 %","format":"{{msg.payload}}","layout":"col-center","style":false,"font":"","fontSize":16,"color":"#717171","wrapText":false,"className":"","value":"payload","valueType":"msg","x":340,"y":400,"wires":[]},{"id":"3c702113cdebf002","type":"ui-chart","z":"97c4136d2899d345","group":"bf0acc3b3c4329c9","name":"グラフ","label":"","order":4,"chartType":"line","category":"topic","categoryType":"msg","xAxisLabel":"","xAxisProperty":"","xAxisPropertyType":"timestamp","xAxisType":"time","xAxisFormat":"","xAxisFormatType":"{HH}:{mm}","xmin":"","xmax":"","yAxisLabel":"","yAxisProperty":"payload","yAxisPropertyType":"msg","ymin":"0","ymax":"100","bins":10,"action":"append","stackSeries":false,"pointShape":"dash","pointRadius":"0","showLegend":false,"removeOlder":1,"removeOlderUnit":"86400","removeOlderPoints":"","colors":["#0095ff","#ff0000","#ff7f0e","#2ca02c","#a347e1","#d62728","#ff9896","#9467bd","#c5b0d5"],"textColor":["#666666"],"textColorDefault":true,"gridColor":["#404040"],"gridColorDefault":false,"width":"6","height":"3","className":"","interpolation":"linear","x":330,"y":440,"wires":[[]]},{"id":"0821f3d8d67c3f17","type":"mqtt in","z":"97c4136d2899d345","name":"","topic":"pressure","qos":"2","datatype":"auto-detect","broker":"a4ab6ee7518f1403","nl":false,"rap":true,"rh":0,"inputs":0,"x":120,"y":580,"wires":[["fd723ad4d4957cec","4949dd988087e572"]]},{"id":"fd723ad4d4957cec","type":"ui-text","z":"97c4136d2899d345","group":"bf0acc3b3c4329c9","order":5,"width":0,"height":0,"name":"気圧 ","label":"気圧 HPa","format":"{{msg.payload}}","layout":"col-center","style":false,"font":"","fontSize":16,"color":"#717171","wrapText":false,"className":"","value":"payload","valueType":"msg","x":330,"y":580,"wires":[]},{"id":"4949dd988087e572","type":"function","z":"97c4136d2899d345","name":"書込み","func":"global.set(\"out_pressure\", msg.payload);\nglobal.set(\"pressure_update_flg\", \"1\");\nreturn msg;\n","outputs":1,"timeout":0,"noerr":0,"initialize":"","finalize":"","libs":[],"x":330,"y":540,"wires":[[]]},{"id":"40c51f88164d7425","type":"inject","z":"97c4136d2899d345","name":"AM6:00~PM21:00 外温度・湿度・気圧報告","props":[{"p":"payload"},{"p":"topic","vt":"str"}],"repeat":"","crontab":"0 6-20 * * *","once":false,"onceDelay":0.1,"topic":"","payload":"1","payloadType":"str","x":260,"y":780,"wires":[["03571a679351f5a3"]]},{"id":"03571a679351f5a3","type":"function","z":"97c4136d2899d345","name":"データー読み込み、言語選択、送出","func":"var number = msg.payload;\nvar data =\"\";\nvar out_temp = \"\";\nvar out_humid = \"\";\nvar out_press = \"\";\nlet temp_bad = false;\nlet humi_bad = false;\nlet press_bad = false;\nvar out_temp_update_flg = global.get(\"temperatur_update_flg\");\nif (out_temp_update_flg != \"0\") {out_temp = global.get(\"out_temperature\");global.set(\"temperature_update_flg\",\"0\");}else{temp_bad = true;}\nvar out_humid_update_flg = global.get(\"humidity_update_flg\");\nif (out_humid_update_flg != \"0\") {out_humid = global.get(\"out_humidity\"); global.set(\"humidity_update_flg\", \"0\"); } else { humi_bad = true; }\nvar out_press_update_flg = global.get(\"pressure_update_flg\");\nif (out_press_update_flg != \"0\") {out_press = global.get(\"out_pressure\"); global.set(\"pressure_update_flg\", \"0\"); } else { press_bad = true; }\n\nlet date = new Date();\nvar hour = date.getHours();\n\n\nif(number == \"1\"){\n     msg.language = \"ja\";\n     if (temp_bad) { data = \"屋外の温度センサーが機能していません、点検して下さい。\"; } else {\n        data = \"現在の時刻は\" + hour + \"時です。\" +\n        \"外気温は\" + out_temp + \"度。\" +\n        \"湿度は\" + out_humid + \"パーセント。\" +\n        \"気圧は\" + out_press + \"ヘクトパスカルです。\";\n}\n}\nif(number == \"2\"){\n     msg.language =\"en\";   \n     if (temp_bad) { data = \"The outdoor temperature sensor is not working, please check.\"; } else { \n        data = \"The time is \" + hour + \" o'clock. \" +\n        \"The temperature is \" + out_temp + \" degrees. \" +\n        \"Humidity is \" + out_humid + \" percent.\";\n        \"The atmospheric pressure is \" + out_press + \"Hectopascals.\";\n}  \n}\nmsg.payload = data;\nreturn msg;\n","outputs":1,"timeout":0,"noerr":0,"initialize":"","finalize":"","libs":[],"x":650,"y":780,"wires":[["023adc96176b7b99"]]},{"id":"023adc96176b7b99","type":"cast-to-client","z":"97c4136d2899d345","name":"","url":"","contentType":"","message":"","language":"","ip":"192.168.1.100","port":"","volume":"","x":950,"y":780,"wires":[[]]},{"id":"a4ab6ee7518f1403","type":"mqtt-broker","name":"","broker":"192.168.1.200","port":"1883","clientid":"","autoConnect":true,"usetls":false,"protocolVersion":4,"keepalive":60,"cleansession":true,"autoUnsubscribe":true,"birthTopic":"","birthQos":"0","birthRetain":"false","birthPayload":"","birthMsg":{},"closeTopic":"","closeQos":"0","closeRetain":"false","closePayload":"","closeMsg":{},"willTopic":"","willQos":"0","willRetain":"false","willPayload":"","willMsg":{},"userProps":"","sessionExpiry":""},{"id":"bf0acc3b3c4329c9","type":"ui-group","name":"温度・湿度・気圧","page":"57fb0c8fb3ed880f","width":6,"height":1,"order":1,"showTitle":true,"className":"","visible":"true","disabled":"false","groupType":"default"},{"id":"57fb0c8fb3ed880f","type":"ui-page","name":"気象","ui":"0bdcc053b020d9d6","path":"/page1","icon":"home","layout":"grid","theme":"2eaf27c68cb57512","breakpoints":[{"name":"Default","px":"0","cols":"3"},{"name":"Tablet","px":"576","cols":"6"},{"name":"Small Desktop","px":"768","cols":"9"},{"name":"Desktop","px":"1024","cols":"12"}],"order":1,"className":"","visible":"true","disabled":"false"},{"id":"0bdcc053b020d9d6","type":"ui-base","name":"気象観測","path":"/dashboard","appIcon":"","includeClientData":true,"acceptsClientConfig":["ui-notification","ui-control"],"showPathInSidebar":false,"headerContent":"page","navigationStyle":"default","titleBarStyle":"default","showReconnectNotification":true,"notificationDisplayTime":1,"showDisconnectNotification":true,"allowInstall":false},{"id":"2eaf27c68cb57512","type":"ui-theme","name":"Default Theme","colors":{"surface":"#405959","primary":"#0094ce","bgPage":"#2c2c2c","groupBg":"#2c2c2c","groupOutline":"#2c2c2c"},"sizes":{"density":"compact","pagePadding":"12px","groupGap":"12px","groupBorderRadius":"4px","widgetGap":"12px"}},{"id":"11d8eacaf48bb3fe","type":"global-config","env":[],"modules":{"@flowfuse/node-red-dashboard":"1.30.2","node-red-contrib-cast":"0.2.17"}}]