subaruのアイコン画像
subaru 2022年09月26日作成
製作品 製作品 閲覧数 515
subaru 2022年09月26日作成 製作品 製作品 閲覧数 515

SPRESENSEで植物の状況をセンシングしELTRESで送信してみた。

SPRESENSEで植物の状況をセンシングしELTRESで送信してみた。

動機
観葉植物を育てている中で外にいるときも植物の状況を知りたい、そして植物と喋ってみたいと考えた。そこで今回はSPRESENSEとELTRESを用いて植物の周りの気温、湿度などといった情報をセンシングし、遠隔で見れるようにすることを目的とした。

使用部品

  • spresense
  • ELTRESアドオンボード
  • GPS受信部分
  • ELTRES送信部分
  • SCD4xセンサ

設計図
キャプションを入力できます
ハードは上記のように作った。
spresense上にセンサとELTRESを乗せ、はめたものである。
ELTRESはLPWA通信で省電でデータを送ることができ、またそれに対応したCLIP Viewer Liteでそのデータを閲覧することができる。今回はそれを実行し植物の水分状態を把握する。

ソースコード

#include <EltresAddonBoard.h>
#include <SensirionI2CScd4x.h>
#include <Wire.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
SensirionI2CScd4x scd4x;


// PIN定義:LED(プログラム状態)
#define LED_RUN PIN_LED0
// PIN定義:LED(GNSS電波状態)
#define LED_GNSS PIN_LED1
// PIN定義:LED(ELTRES状態)
#define LED_SND PIN_LED2
// PIN定義:LED(エラー状態)
#define LED_ERR PIN_LED3

// プログラム内部状態:初期状態
#define PROGRAM_STS_INIT      (0)
// プログラム内部状態:起動中
#define PROGRAM_STS_RUNNING   (1)
// プログラム内部状態:終了処理中
#define PROGRAM_STS_STOPPING  (2)
// プログラム内部状態:終了
#define PROGRAM_STS_STOPPED   (3)

// プログラム内部状態
int program_sts = PROGRAM_STS_INIT;
// GNSS電波受信タイムアウト(GNSS受信エラー)発生フラグ
bool gnss_recevie_timeout = false;
// 点滅処理で最後に変更した時間
uint64_t last_change_blink_time = 0;
// イベント通知での送信直前通知(5秒前)受信フラグ
bool event_send_ready = false;
// イベント通知でのアイドル状態受信フラグ
bool event_idle = false;
// 送信回数
int send_count = 0;
// ペイロードデータ格納場所
uint8_t payload[16];

/**
 * @brief イベント通知受信コールバック
 */
void eltres_event_cb(eltres_board_event event) {
  switch (event) {
  case ELTRES_BOARD_EVT_GNSS_TMOUT:
    // GNSS電波受信タイムアウト
    Serial.println("gnss wait timeout error.");
    gnss_recevie_timeout = true;
    break;
  case ELTRES_BOARD_EVT_IDLE:
    // アイドル状態
    Serial.println("waiting sending timings.");
    digitalWrite(LED_SND, LOW);
    event_idle = true;
    break;
  case ELTRES_BOARD_EVT_SEND_READY:
    // 送信直前通知(5秒前)
    Serial.println("Shortly before sending, so setup payload if need.");
    event_send_ready = true;
    break;
  case ELTRES_BOARD_EVT_SENDING:
    // 送信開始
    Serial.println("start sending.");
    digitalWrite(LED_SND, HIGH);
    break;
  case ELTRES_BOARD_EVT_GNSS_UNRECEIVE:
    // GNSS電波未受信
    Serial.println("gnss wave has not been received.");
    digitalWrite(LED_GNSS, LOW);
    break;
  case ELTRES_BOARD_EVT_GNSS_RECEIVE:
    // GNSS電波受信
    Serial.println("gnss wave has been received.");
    digitalWrite(LED_GNSS, HIGH);
    gnss_recevie_timeout = false;
    break;
  case ELTRES_BOARD_EVT_FAULT:
    // 内部エラー発生
    Serial.println("internal error.");
    break;
  }
}

/**
 * @brief GGA情報受信コールバック
 */
static eltres_board_gga_info g_gga_info;
void gga_event_cb(eltres_board_gga_info *gga_info) {
  Serial.print("[gga]");
  if (gga_info->m_pos_status) {
  
    // 測位状態
    // GGA情報をシリアルモニタへ出力
    g_gga_info = *gga_info;
    Serial.print("utc: ");
    Serial.println((const char *)gga_info->m_utc);
    Serial.print("lat: ");
    Serial.print((const char *)gga_info->m_n_s);
    Serial.print((const char *)gga_info->m_lat);
    Serial.print(", lon: ");
    Serial.print((const char *)gga_info->m_e_w);
    Serial.println((const char *)gga_info->m_lon);
    Serial.print("pos_status: ");
    Serial.print(gga_info->m_pos_status);
    Serial.print(", sat_used: ");
    Serial.println(gga_info->m_sat_used);
    Serial.print("hdop: ");
    Serial.print(gga_info->m_hdop);
    Serial.print(", height: ");
    Serial.print(gga_info->m_height);
    Serial.print(" m, geoid: ");
    Serial.print(gga_info->m_geoid);
    Serial.println(" m");
  } else {
    // 非測位状態
    // "invalid data"をシリアルモニタへ出力
    Serial.println("invalid data.");
  }
}

/**
 * @brief setup()関数
 */
void setup() {
  // シリアルモニタ出力設定
  Serial.begin(115200);

  // LED初期設定
  pinMode(LED_RUN, OUTPUT);
  digitalWrite(LED_RUN, HIGH);
  pinMode(LED_GNSS, OUTPUT);
  digitalWrite(LED_GNSS, LOW);
  pinMode(LED_SND, OUTPUT);
  digitalWrite(LED_SND, LOW);
  pinMode(LED_ERR, OUTPUT);
  digitalWrite(LED_ERR, LOW);

  // ELTRES起動処理
  eltres_board_result ret = EltresAddonBoard.begin(ELTRES_BOARD_SEND_MODE_1MIN,eltres_event_cb, gga_event_cb);
  if (ret != ELTRES_BOARD_RESULT_OK) {
    // ELTRESエラー発生
    digitalWrite(LED_RUN, LOW);
    digitalWrite(LED_ERR, HIGH);
    program_sts = PROGRAM_STS_STOPPED;
    Serial.print("cannot start eltres board (");
    Serial.print(ret);
    Serial.println(").");
  } else {
    // 正常
    program_sts = PROGRAM_STS_RUNNING;
  }
    while (!Serial) {
        delay(100);
    }

    Wire.begin();

    uint16_t error;
    char errorMessage[256];

    scd4x.begin(Wire);

    // stop potentially previously started measurement
    error = scd4x.stopPeriodicMeasurement();
    if (error) {
        Serial.print("Error trying to execute stopPeriodicMeasurement(): ");
        errorToString(error, errorMessage, 256);
        Serial.println(errorMessage);
    }

    uint16_t serial0;
    uint16_t serial1;
    uint16_t serial2;
    error = scd4x.getSerialNumber(serial0, serial1, serial2);
    if (error) {
        Serial.print("Error trying to execute getSerialNumber(): ");
        errorToString(error, errorMessage, 256);
        Serial.println(errorMessage);
    } else {
        printSerialNumber(serial0, serial1, serial2);
    }

    // Start Measurement
    error = scd4x.startPeriodicMeasurement();
    if (error) {
        Serial.print("Error trying to execute startPeriodicMeasurement(): ");
        errorToString(error, errorMessage, 256);
        Serial.println(errorMessage);
    }

    Serial.println("Waiting for first measurement... (5 sec)");
}


void printUint16Hex(uint16_t value) {
    Serial.print(value < 4096 ? "0" : "");
    Serial.print(value < 256 ? "0" : "");
    Serial.print(value < 16 ? "0" : "");
    Serial.print(value, HEX);
}

void printSerialNumber(uint16_t serial0, uint16_t serial1, uint16_t serial2) {
    Serial.print("Serial: 0x");
    printUint16Hex(serial0);
    printUint16Hex(serial1);
    printUint16Hex(serial2);
    Serial.println();
}

void loop() {
  uint32_t gnss_time;
  int32_t remaining;
  uint16_t error;
  char errorMessage[256];

  delay(100);

  uint16_t co2 = 0;
  float temperature = 0.0f;
  float humidity = 0.0f;
  bool isDataReady = false;
  float co2_f = (float)co2;
  uint32_t mcp;
  error = scd4x.readMeasurement(co2, temperature, humidity);
  if (error) {
    Serial.print("Error trying to execute readMeasurement(): ");
    errorToString(error, errorMessage, 256);
    Serial.println(errorMessage);
    return;
  }


  switch (program_sts) {
    case PROGRAM_STS_RUNNING:
      // プログラム内部状態:起動中
      if (gnss_recevie_timeout) {
        // GNSS電波受信タイムアウト(GNSS受信エラー)時の点滅処理
        uint64_t now_time = millis();
        if ((now_time - last_change_blink_time) >= 1000) {
          last_change_blink_time = now_time;
          bool set_value = digitalRead(LED_ERR);
          bool next_value = (set_value == LOW) ? HIGH : LOW;
          digitalWrite(LED_ERR, next_value);
        }
      } else {
        digitalWrite(LED_ERR, LOW);
      }

      if (event_send_ready) {
        // 送信直前通知時の処理
        
        payload[0] = 0x82;
        uint32_t raw;
        memcpy(&raw, &temperature, sizeof(uint32_t));
        printf(" 気温=%g, 湿度=%g, CO2=%d\n", temperature, humidity, co2);
        memcpy(&mcp, &temperature, sizeof(uint32_t));
        printf(" 気温_mcp=%g, %d, %8x\n", temperature, mcp, mcp);
        payload[1] = (uint8_t)((mcp>>24) & 0xff );
        payload[2] = (uint8_t)((mcp>>16) & 0xff );
        payload[3] = (uint8_t)((mcp>>8) & 0xff );
        payload[4] = (uint8_t)((mcp>>0) & 0xff );
        printf("%x%x%x%x \n",payload[1],payload[2],payload[3],payload[4]);
        memcpy(&mcp, &humidity, sizeof(uint32_t));
        printf(" 湿度_mcp=%g, %d, %8x\n", humidity, mcp, mcp);
        payload[5] = (uint8_t)((mcp>>24) & 0xff );
        payload[6] = (uint8_t)((mcp>>16) & 0xff );
        payload[7] = (uint8_t)((mcp>>8) & 0xff );
        payload[8] = (uint8_t)((mcp>>0) & 0xff );
        printf("%x%x%x%x \n",payload[1],payload[2],payload[3],payload[4]);
        co2_f = (float)co2;
        memcpy(&mcp, &co2_f, sizeof(uint32_t));
        printf(" CO2=%g, %d, %8x\n", co2_f, mcp, mcp);
        payload[9] = (uint8_t)((mcp>>24) & 0xff );
        payload[10] = (uint8_t)((mcp>>16) & 0xff );
        payload[11] = (uint8_t)((mcp>>8) & 0xff );
        payload[12] = (uint8_t)((mcp>>0) & 0xff );
        printf("%x%x%x%x \n\n",payload[1],payload[2],payload[3],payload[4]);
        EltresAddonBoard.set_payload(payload);


      }
      if (event_idle) {
        // 送信完了時の処理
        event_idle = false;
        if (send_count > 4) {
           // 規定回数
           program_sts = PROGRAM_STS_STOPPING;
           Serial.println("stop sending.");
           break;
        }
        // GNSS時刻(epoch秒)の取得
        EltresAddonBoard.get_gnss_time(&gnss_time);
        Serial.print("gnss time: ");
        Serial.print(gnss_time);
        Serial.println(" sec");
        // 次送信までの残り時間の取得
        EltresAddonBoard.get_remaing_time(&remaining);
        Serial.print("remaining time: ");
        Serial.print(remaining);
        Serial.println(" sec");
      }
      break;
      
    case PROGRAM_STS_STOPPING:
      // プログラム内部状態:終了処理中
      // ELTRES停止処理(注意:この処理を行わないとELTRESが自動送信し続ける)
      EltresAddonBoard.end();
      digitalWrite(LED_RUN, LOW);
      digitalWrite(LED_GNSS, LOW);
      program_sts = PROGRAM_STS_STOPPED;
      break;
      
    case PROGRAM_STS_STOPPED:
      // プログラム内部状態:終了
      break;
  }
  // 次のループ処理まで100ミリ秒待機
  delay(100);
}

結果
無事データをELTRESを用いてCLIP Viewer Liteに送信できた。
数日間データを取っていたが水をあげているタイミングで急激に上昇していることがわかる。逆に水を上げていない昼間は急激に下がっていることがわかる。これらの結果から遠隔でも植物の状況が客観的に理解できると結論づける。
キャプションを入力できます キャプションを入力できます

今後の課題
今回では周りの湿度は把握することができたが、植物に影響する要素としては光などといったものもあり今後はそういったデータも送信できるようにしたい。また植物の育成具合を定期的に写真で撮影し、SPRESENSEのNNCで画像認識を行いどれくらい増えたか、成長したかといったデータも送れるようにしたい。
また現状遠隔で把握する手段がCLIPVIewLiteしかないのでAPIを通じてLINEなどで確認できるようにしたい。

1
  • subaru さんが 2022/09/26 に 編集 をしました。 (メッセージ: 初版)
ログインしてコメントを投稿する