cvl-robot's diary

研究ノート メモメモ https://github.com/dotchang/

Arduino MKR IoT CarrierとGrove SCD30搭載CO2センサを使って、長期間の環境測定をしてみる。

人間と共生するロボットは、24時間365日電源を切らないで稼働しなければならないと思うけれど、メンテナンスや省エネのために人間の睡眠に相当するような休息は必要で、ロボットはいつ寝るべきかを調べたいので、お手軽に環境測定と記録が可能なデバイスを用意してみました。
ハードウェアもクラウド連携のソフトウェアも、とても良くできているので、ほとんど買ってくるだけで完了しますが、備忘録のためのソースコードを残します。

購入したもの

KEEPPOWER 3500mAh P1835J 保護回路付き リチウムイオンバッテリー 日本製セル (2本組)
f:id:cvl-robot:20211116143419j:plain
Arduino MKR IoT CarrierとGrove SCD30搭載CO2センサ(表)
f:id:cvl-robot:20211116143431j:plain
Arduino MKR IoT CarrierとGrove SCD30搭載CO2センサ(裏)

用意したもの

WiFi接続のインターネット
Arduino IoT Cloudの有料サービス(1000円/月ぐらいのやつ)

制限

時間はUTCです。JSTに直すのは、面倒くさい処理のオーバーヘッドになるので止めました。
また、ミリ秒以下が取れていません。単純にその秒内での何回目を示すカウンタで代替しています。

ソースコード

// WiFiNINA - Version: 1.8.13
#include <WiFiNINA.h>
#include <RTCZero.h>
int day;
int month;
int year;
int hours;
int minutes;
int seconds;
int millisCount;

#include "thingProperties.h"
#include <Arduino_MKRIoTCarrier.h>
MKRIoTCarrier carrier;

#include "SCD30.h"

#if defined(ARDUINO_ARCH_AVR)
#pragma message("Defined architecture for ARDUINO_ARCH_AVR.")
#define SERIAL Serial
#elif defined(ARDUINO_ARCH_SAM)
#pragma message("Defined architecture for ARDUINO_ARCH_SAM.")
#define SERIAL SerialUSB
#elif defined(ARDUINO_ARCH_SAMD)
#pragma message("Defined architecture for ARDUINO_ARCH_SAMD.")
#define SERIAL SerialUSB
#elif defined(ARDUINO_ARCH_STM32F4)
#pragma message("Defined architecture for ARDUINO_ARCH_STM32F4.")
#define SERIAL SerialUSB
#else
#pragma message("Not found any architecture.")
#define SERIAL Serial
#endif

File myFile;
String filename;


/* Create an rtc object */
RTCZero rtcz;

void loopRTC()
{
  static int lastSeconds = -1;

  day = rtcz.getDay();
  month = rtcz.getMonth();
  year = rtcz.getYear();
  hours = rtcz.getHours();
  minutes = rtcz.getMinutes();
  seconds = rtcz.getSeconds();

  // Print date...
  print2digits(day);
  Serial.print("/");
  print2digits(month);
  Serial.print("/");
  print2digits(year);
  Serial.print(" ");

  // ...and time
  print2digits(hours);
  Serial.print(":");
  print2digits(minutes);
  Serial.print(":");
  print2digits(seconds);
  Serial.print(":");
  if (lastSeconds != seconds) {
    millisCount = 0;
  }
  else {
    millisCount += 1;
  }
  lastSeconds = seconds;
  print2digits(millisCount);

  Serial.println();
}

void print2digits(int number) {
  if (number < 10) {
    Serial.print("0"); // print a 0 before if the number is < than 10
  }
  Serial.print(number);
}

void loopSCD() {
  float result[3] = {0};

  if (scd30.isAvailable()) {
    scd30.getCarbonDioxideConcentration(result);
    CO2Grove = result[0];
    temperatureGrove = result[1];
    humidityGrove = result[2];
  }
}

void setup() {
  // Initialize serial and wait for port to open:
  Serial.begin(115200);
  // This delay gives the chance to wait for a Serial Monitor without blocking if none is found
  delay(1500);

  Serial.println("SCD30 Raw Data");
  scd30.initialize();

  //connect to WiFi
  Serial.print("Connecting to ");
  Serial.println(SSID);
  WiFi.begin(SSID, PASS);
  while (WiFi.status() != WL_CONNECTED) {
    delay(500);
    Serial.print(".");
  }
  Serial.println(" CONNECTED");

  rtcz.begin(); //+
  unsigned long epoch;
  int numberOfTries = 0, maxTries = 16;
  do {
    epoch = WiFi.getTime();
    numberOfTries++;
  }
  while ((epoch == 0) && (numberOfTries < maxTries));
  Serial.println(epoch);
  Serial.println(numberOfTries);
  if (numberOfTries == maxTries) {
    Serial.print("NTP unreachable!!");
    //while (1);
  }
  else {
    Serial.print("Epoch received: ");
    rtcz.setEpoch(epoch);
    Serial.println();
  }

  //disconnect WiFi as it's no longer needed
  WiFi.end();

  // Defined in thingProperties.h
  initProperties();

  // Connect to Arduino IoT Cloud
  ArduinoCloud.begin(ArduinoIoTPreferredConnection);
  //Get Cloud Info/errors , 0 (only errors) up to 4
  setDebugMessageLevel(2);
  ArduinoCloud.printDebugInfo();

  //Wait to get cloud connection to init the carrier
  while (ArduinoCloud.connected() != 1) {
    ArduinoCloud.update();
    delay(500);
  }

  delay(500);
  CARRIER_CASE = false;
  carrier.begin();
  carrier.display.setRotation(0);
  delay(1500);

  // open the file. note that only one file can be open at a time,
  // so you have to close this one before opening another.
  SDwritable = false;
  loopRTC();
  String dates = String(month) + String(day);
  String times = String(hours) + String(minutes); // within 8 characters + 3 charcters
  filename = dates + times + String(".txt");
  myFile = SD.open(filename);
  if (myFile) {
    Serial.print("Opening ");
    Serial.println(filename.c_str());
    myFile.println("Year, Month, Day, Hours, Minutes, Seconds, MillisCount, temperature, humidity, pressure, light, r, g, b, Gx, Gy, Gz, Ax, Ay, Az, CO2Grove, temperatureGrove, humidityGrove, batteryVoltage");
    myFile.close();

    SDwritable = true;
  }
}

void loop() {
  ArduinoCloud.update();
  carrier.Buttons.update();

  loopRTC();
  loopSCD();

  temperature = carrier.Env.readTemperature();
  humidity = carrier.Env.readHumidity();
  pressure = carrier.Pressure.readPressure();
  carrier.IMUmodule.readGyroscope(Gx, Gy, Gz);
  carrier.IMUmodule.readAcceleration(Ax, Ay, Az);

  batteryVoltage = analogRead(ADC_BATTERY) * 3.3f / 1023.0f / 1.2f * (1.2f + 0.33f); // https://mag.switch-science.com/2021/10/07/arduino-mkr-wifi-1010/
  battery = (batteryVoltage - 2.8f) / (4.208f - 2.8f) * 100; //for battery available in %

  while (!carrier.Light.colorAvailable()) {
    delay(5);
  }
  carrier.Light.readColor(r, g, b, light);

  if (carrier.Buttons.onTouchDown(TOUCH0)) {
    carrier.display.fillScreen(ST77XX_WHITE);
    carrier.display.setTextColor(ST77XX_RED);
    carrier.display.setTextSize(2);

    carrier.display.setCursor(20, 80);
    carrier.display.print("Temp: ");
    carrier.display.print(temperature);
    carrier.display.print(" C");

    carrier.display.setCursor(20, 120);
    carrier.display.print("TempGrove: ");
    carrier.display.print(temperatureGrove);
    carrier.display.print(" C");
  }

  if (carrier.Buttons.onTouchDown(TOUCH1)) {
    carrier.display.fillScreen(ST77XX_WHITE);
    carrier.display.setTextColor(ST77XX_RED);
    carrier.display.setTextSize(2);

    carrier.display.setCursor(20, 80);
    carrier.display.print("Humi: ");
    carrier.display.print(humidity);
    carrier.display.print(" %");

    carrier.display.setCursor(20, 120);
    carrier.display.print("HumiGrove: ");
    carrier.display.print(humidityGrove);
    carrier.display.print(" %");
  }

  if (carrier.Buttons.onTouchDown(TOUCH2)) {
    carrier.display.fillScreen(ST77XX_WHITE);
    carrier.display.setTextColor(ST77XX_RED);
    carrier.display.setTextSize(2);

    carrier.display.setCursor(30, 80);
    carrier.display.print("Light: ");
    carrier.display.print(light);

    carrier.display.setCursor(30, 110);
    carrier.display.print("RGB: ");
    carrier.display.setCursor(30, 125);
    carrier.display.print(r);
    carrier.display.setCursor(30, 140);
    carrier.display.print(g);
    carrier.display.setCursor(30, 155);
    carrier.display.print(b);
  }

  if (carrier.Buttons.onTouchDown(TOUCH3)) {
    carrier.display.fillScreen(ST77XX_WHITE);
    carrier.display.setTextColor(ST77XX_RED);
    carrier.display.setTextSize(2);

    carrier.display.setCursor(30, 80);
    carrier.display.print("Pressure: ");
    carrier.display.print(pressure);

    carrier.display.setCursor(30, 110);
    carrier.display.print("CO2Grove: ");
    carrier.display.print(CO2Grove);
  }

  if (carrier.Buttons.onTouchDown(TOUCH4)) {
    carrier.display.fillScreen(ST77XX_WHITE);
    carrier.display.setTextColor(ST77XX_RED);
    carrier.display.setCursor(70, 30);
    carrier.display.print("Gyroscope: ");
    carrier.display.setCursor(50, 45);
    carrier.display.print(Gx);
    carrier.display.setCursor(50, 60);
    carrier.display.print(Gy);
    carrier.display.setCursor(50, 75);
    carrier.display.print(Gz);

    carrier.display.setCursor(50, 120);
    carrier.display.print("Accelerometer: ");
    carrier.display.setCursor(50, 135);
    carrier.display.print(Ax);
    carrier.display.setCursor(50, 150);
    carrier.display.print(Ay);
    carrier.display.setCursor(50, 165);
    carrier.display.print(Az);
  }

  // if the file opened okay, write to it:
  myFile = SD.open(filename, FILE_WRITE);
  if (myFile) {
    myFile.print(year);
    myFile.print(",");
    myFile.print(month);
    myFile.print(",");
    myFile.print(day);
    myFile.print(",");
    myFile.print(hours);
    myFile.print(",");
    myFile.print(minutes);
    myFile.print(",");
    myFile.print(seconds);
    myFile.print(",");
    myFile.print(millisCount);
    myFile.print(", ");

    myFile.print(temperature);
    myFile.print(",");
    myFile.print(humidity);
    myFile.print(",");
    myFile.print(pressure);
    myFile.print(",");
    myFile.print(light);
    myFile.print(",");
    myFile.print(r);
    myFile.print(",");
    myFile.print(g);
    myFile.print(",");
    myFile.print(b);
    myFile.print(",");
    myFile.print(Gx);
    myFile.print(",");
    myFile.print(Gy);
    myFile.print(",");
    myFile.print(Gz);
    myFile.print(",");
    myFile.print(Ax);
    myFile.print(",");
    myFile.print(Ay);
    myFile.print(",");
    myFile.print(Az);
    myFile.print(",");
    myFile.print(CO2Grove);
    myFile.print(",");
    myFile.print(temperatureGrove);
    myFile.print(",");
    myFile.print(humidityGrove);
    myFile.print(",");
    myFile.println(batteryVoltage);

    // close the file:
    myFile.close();

    SDwritable = true;
  } else {
    // if the file didn't open, print an error:
    Serial.print("error opening ");
    Serial.println(filename.c_str());

    SDwritable = false;
  }

  if (humidity >= 60 && temperature >= 15) {
    weather_report = "It is very humid outside";

  } else if (temperature >= 15 && light >= 700) {
    weather_report = "Warm and sunny outside";

  } else if (temperature <= 16 && light >= 700) {
    weather_report = "A little cold, but sunny outside";
  }

  else {
    weather_report = "Weather is normal";
  }

}
/*
  Since TemperatureGrove is READ_WRITE variable, onTemperatureGroveChange() is
  executed every time a new value is received from IoT Cloud.
*/
void onTemperatureGroveChange()  {
  // Add your code here to act upon TemperatureGrove change
}

Arduino IoT Cloudのダッシュボード画面

出来上がるとこんな感じ。メーター類は適当に配置。

f:id:cvl-robot:20211116143152p:plain
Arduino MKR IoT Carrierのサンプル画面

12月2日追記

Groveセンサーを付けていると、2時間ぐらいでハングアップしてしまいます。まだ、原因不明。