ATOM LiteでIoT温湿度計を作る（WiFi切断対策版）

商品として販売されているIoT温湿度計もあるが，自作すれば半額くらいで作ることができる。ネット記事[^1]を参考にATOM Liteを使って作ってみたのだが，時間が経つとWiFiが切断されて記録されなくなってしまう問題が多発した。調べてみると，ATOM Liteはデフォルトの関数を使うとWiFi接続が落ちた場合自動復帰しないことがあるので回避策が必要とのこと。そこで別のサイト[^2]を参考にプログラムを書きなおしてみた。とりあえず思ったように動作を続けているようなので忘備録として記事にしておく。

ハードウェアとしては，M5Stackシリーズで最も小さいATOM Liteと，M5Stack用の環境センサユニット(ENV II Unit)を使う。IoTのクラウドサービスには[Ambient](https://ambidata.io/)を使う。

ハード的にはATOM Liteと環境センサを付属のケーブルでつなぐだけで完成。あとはプログラムを書きこんで，USBから給電すればOK。

```C:atom_lite_BMP280.ino
#include <Wire.h>
#include "Adafruit_Sensor.h"
#include <Adafruit_BMP280.h>
#include "SHT3X.h"
#include <FastLED.h>
#include <WiFi.h>
#include "Ambient.h"

#define WIFI_SSID "Your_WiFi_SSID" 
#define WIFI_PASS "Your_WiFi_PSK" 

SHT3X sht30;
Adafruit_BMP280 bme;
WiFiClient client;
Ambient ambient;

const int NUM_LEDS = 1;
const int LED_PIN = 27;
static CRGB leds[NUM_LEDS];

unsigned int channelId = 00000; // Ambient channel ID
const char* writeKey = "Your_write_key"; // Ambient write key

float tmp = 0.0;
float hum = 0.0;
float pressure = 0.0;

unsigned long previousMillis = 0;
const long interval = 300000;
//const long interval = 10000;

void setup() {
  // LED
  FastLED.addLeds<WS2812, LED_PIN, GRB>(leds, NUM_LEDS);
  FastLED.setBrightness(1);
  leds[0] = CRGB::Black;
  FastLED.show();

  // Serial
  Serial.begin(115200);
  Serial.flush();

  Serial.println("");  
  Wire.begin(26,32);
  Serial.println("\nM5 Atom Lite + ENV Unit -> Ambient");
  while (!bme.begin(0x76)){  
    Serial.println("Could not find a valid BMP280 sensor, check wiring!");
  }
  ambient.begin(channelId,writeKey,&client);
}

void loop() {
  unsigned long currentMillis = millis();
  if (WiFi.status() == WL_CONNECTED) {
    if (currentMillis - previousMillis >= interval) {
      previousMillis = currentMillis;
      pressure = bme.readPressure();
      if(sht30.get()==0){
        tmp = sht30.cTemp;
        hum = sht30.humidity;
      }
      Serial.printf("Temperature: %2.2f*C  Humidity: %0.2f%%  Pressure: %0.2fPa\r\n", tmp, hum, pressure);
      ambient.set(1,String(tmp).c_str());
      ambient.set(2,String(hum).c_str());
      ambient.set(3,String(pressure).c_str());
      ambient.send();
    }
  } else {
    leds[0] = CRGB::Red;
    FastLED.show();
    WiFi.begin(WIFI_SSID, WIFI_PASS);
    int wifiReconnectCount = 0;
    while (WiFi.status() != WL_CONNECTED) {
      if (wifiReconnectCount >= 200) { ESP.restart(); }
      wifiReconnectCount++;
      delay(100);
    }
    Serial.println("It's now online.");
    leds[0] = CRGB::Green;
    FastLED.show();
  }
}
```
プログラム中のWIFI_SSIDやWIFI_PASSは無線LANの設定，channelIdとwriteKeyにはAmbientから提供されるものを使う。

Ambient上でもグラフを描くことはできるのだが，今回は任意の日付や期間のグラフが描きたいのでAPIを呼び出して取得したデータを[Google Charts](https://developers.google.com/chart)を使って描画してみた。

```HTML:ambi_chart.html
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN" "https://www.w3.org/TR/html4/strict.dtd">
<html>

<head>
<meta charset="utf-8" content="">
<title>Ambient - GoogleChart</title> 
<script src="http://ajax.googleapis.com/ajax/libs/jquery/1.11.0/jquery.min.js" type="text/javascript" ></script>
<script type="text/javascript" src="https://www.google.com/jsapi"></script>
</head>

<body>

<!-- 入力した任意の日付のグラフを作成するためのフォーム -->
<div align="center">
<form action="ambi_chart.html" method="get">From <input type="text" name="from" id="text1" value="" /> ~ To <input type="text" name="to" id="text2" value="" /><input type="submit" value="send" /><br />
</div>

<script type="text/javascript">
<!-- Ambidata.ioを利用するためのidやkeyの設定 -->
const ch_id = 00000;
const read_key = 'Your_read_key';

<!-- Ambidata.io APIのベースURL -->
const base_url = 'http://ambidata.io/api/v2/channels/';

<!-- フォームで送信されたパラメータをgetするための関数 -->
function getParam(name, url) {
    if (!url) url = window.location.href;
    name = name.replace(/[\[\]]/g, "\\$&");
    var regex = new RegExp("[?&]" + name + "(=([^&#]*)|&|#|$)"),results = regex.exec(url);
    if (!results) return null;
    if (!results[2]) return '';
    return decodeURIComponent(results[2].replace(/\+/g, " "));
}

<!-- 今日の日付 -->
var today = new Date(); 
var today_str = today.getFullYear()+"-"+('0'+(today.getMonth()+1)).slice(-2)+"-"+('0'+today.getDate()).slice(-2);

<!-- パラメータを確認。from，toの両方がセットされていない場合は今日の温度ログを表示。 -->
if (getParam('from') == null || getParam('to') == null) {
    document.getElementById("text1").setAttribute("value",today_str);
    document.getElementById("text2").setAttribute("value",today_str);
    var ambi_url = base_url+ch_id+'/data?readKey='+read_key+'&date=\"'+today_str+'\"';
} else {
    document.getElementById("text1").setAttribute("value",getParam('from'));
    document.getElementById("text2").setAttribute("value",getParam('to'));
    <!-- Ambidata.ioではtoで指定した前日までの記録しか返さないので+1日する -->
    var date_to = new Date(getParam('to'));
    date_to.setDate(date_to.getDate()+1);
    var date_to_str = date_to.getFullYear()+"-"+('0'+(date_to.getMonth()+1)).slice(-2)+"-"+('0'+date_to.getDate()).slice(-2);
    var ambi_url = base_url+ch_id+'/data?readKey='+read_key+'&start=\"'+getParam('from')+'\"&end=\"'+date_to_str+'\"';
}

<!-- 以下Google Charts関係 -->
google.load("visualization", "1", {packages:["corechart"]});
google.setOnLoadCallback(drawChart);

function drawChart() {
    var dataArray = [['Date time','Temp']];
    var df = $.Deferred();
    <!-- Ambidata.ioからJSONデータを読み出してGoogle Charts用の配列に書き出し -->
    $(function() {
        $.ajax({
            url: ambi_url,
            dataType : 'json',
            cache:false,
        }).done(function(data){
        console.log("success");
        $(data).each(function(){
            <!-- 一つのチャンネルに複数の系統のデータをまとめているので，目的のデータが入っているか確認しながら配列に入れる -->
            if(this.d1) {
                var data_item = [new Date(this.created),this.d1];
                dataArray.push(data_item);
            }
        });
        df.resolve();
        }).fail(function(){
            console.log("error");
        });
    });

    df.done(function(){
        var chartdata = google.visualization.arrayToDataTable(dataArray);
        var options = {
            title: 'Temperature Log',
            hAxis: {title: 'Date/time'},
            vAxis: {title: 'Temperature (deg)'},
            legend: 'none'
        };
        <!-- タイムゾーンを日本時間にする -->
        var dateFormat = new google.visualization.DateFormat({formatType: "long", timezone: +9});
        var chart = new google.visualization.LineChart(document.getElementById('chart_div'));
        chart.draw(chartdata, options);
    });
}
</script> 

<div id="chart_div" style="width: 700px; height: 500px;" align="center"></div>

</body>
</html>
```

URLパラメータを取得する関数はここ[^3]から拝借しました。


[^1]: [ATOM Liteでできること 〜ATOM Liteで環境センサユニットを使う](https://msr-r.net/atom-lite-env-unit/) 
[^2]: [ATOM Lite(ESP32)でWiFiに常時接続してHTTP POSTする](https://ebith.hatenablog.jp/entry/2020/06/07/204312)
[^3]: [Javascript でURLのパラメータを取得する方法](https://www-creators.com/archives/4463)