ramshinkのアイコン画像
ramshink 2022年09月26日作成 (2022年09月26日更新)
製作品 製作品 閲覧数 635
ramshink 2022年09月26日作成 (2022年09月26日更新) 製作品 製作品 閲覧数 635

SPRESENSEで温度モニタ、デバイス制御

SPRESENSEで温度モニタ、デバイス制御

室温に応じて自宅の機器を自動で動かしたい

SPRESENSEの各拡張ボードを使用してシステムを構築する。

温湿度センサで室内の温度をモニタリングする。
温度に応じてカメラの情報をもとにドアの開閉を促したり、エアコンのON/OFF、サーキュレーターやミストのON/OFFを制御する。
外出中にトラブルが発生した場合も、LPWA通信により自宅が停電しているかどうかが確認できる。

部品

・SPRESENSE本体     :1
・SPRESENSE拡張ボード  :1
・ELTRESアドオンボード  :1
・SPRESENSEカメラモジュール:1
モバイルバッテリーAC一体:1
・USB-ACアダプタ     :1
・100均スピーカー(セリア) :1
温湿度センサAM2301b  :1
・GoogleHOME      :1
Nature REMO      :1
・SWITCH BOTプラグ   :2
ガーデンミストキット  :1 
・サーキュレーター    :1
GPSアンテナ      :1 ※
GPSアンテナ変換ケーブル:1 ※
※提供いただいたELTERSアドオンボードのサンプルにアンテナを同梱いただいていましたが、
屋内で使用する場合にはGPSを受信できなかったため、別途購入しました。

システム概要

温度に応じてサーキュレータ、ミストをON/OFFする。
それぞれのON/OFFはSPRESENSEに接続されたスピーカーからの音声指示をGoogleHOMEが認識し、
GoogleHome/SwitchBot連携によりSWITCH BOTプラグがON/OFFすることでサーキュレータ、ミストがON/OFFする。
エアコンのON/OFFは上記と同様に音声指示によりGoogleHOMEが認識し、
GoogleHOME/NatureREMO連携により、Nature REMOの赤外線リモコンでエアコンをON/OFFさせる。
SPRESENSEはモバイルバッテリー一体型ACアダプタで常時給電されている。停電時でもモバイルバッテリーとして作動するため、しばらくの間は動作することができる。
電源とは別にACアダプタからUSBの5VがSPRESENSEのADCに接続されており、停電時はUSBの5V出力が0Vになる。
そのさいはLPWA通信のエラー検出ペイロードを使用して、スマホに停電していることを知らせる。

<構成図>

キャプションを入力できます

<構成写真>
・SPRESENSE周辺
キャプションを入力できます

・ポンプ
キャプションを入力できます

・雨水タンク(中にポンプが入っており、ホーズがガーデンミストに接続している)
キャプションを入力できます

・ガーデンミスト

ここに動画が表示されます

ソースコード

//ELTRES
#include <EltresAddonBoard.h>

// 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 イベント通知受信コールバック
 * @param event イベント種別
 */
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情報受信コールバック
 * @param gga_info GGA情報のポインタ
 */
void gga_event_cb(const eltres_board_gga_info *gga_info) {
  Serial.print("[gga]");
  if (gga_info->m_pos_status) {
    // 測位状態
    // GGA情報をシリアルモニタへ出力
    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.");
  }
}


//ELTRES end //////


//aht

#include <Adafruit_AHTX0.h>
Adafruit_AHTX0 aht;
//aht end////

//mp3

#include <SDHCI.h>
#include <Audio.h>

SDClass theSD;
AudioClass *theAudio;

File myFile;

bool ErrEnd = false;

static void audio_attention_cb(const ErrorAttentionParam *atprm)
{
  puts("Attention!");
  
  if (atprm->error_code >= AS_ATTENTION_CODE_WARNING)
    {
      ErrEnd = true;
   }
}
//mp3 end////

//cam
#include <Camera.h>
#include "Adafruit_ILI9341.h"

#define TFT_DC 9
#define TFT_CS 10
Adafruit_ILI9341 display = Adafruit_ILI9341(TFT_CS, TFT_DC);

void CamCB(CamImage img) {
  if (img.isAvailable()) {
    img.convertPixFormat(CAM_IMAGE_PIX_FMT_RGB565);
    display.drawRGBBitmap(0, 0 /* 開始座標 */
        , (uint16_t*)img.getImgBuff() /* 画像データ */ 
        , 320, 240); /* 横幅、縦幅  */
  }
}
//cam end//////

//電源状態
int powstatus = 1;

void setup() {
  //ELTRES
  // 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;
  }
  //ELTRES end /////
  
  //aht
  Serial.begin(115200);
  Serial.println("Adafruit AHT10/AHT20 demo!");

  if (! aht.begin()) {
    Serial.println("Could not find AHT? Check wiring");
    while (1) delay(10);
  }
  Serial.println("AHT10 or AHT20 found");


  int AC = 0; //AirConditioner status
  int FAN = 0; //FAN status
  int Mist = 0; //Mist status
  //aht end//////

  //ADC
      int sensorValue = analogRead(A0);
    int sensorValue1 = analogRead(A1);
    int sensorValue2 = analogRead(A2);
 // Convert the analog reading (which goes from 0 - 1023) to a voltage (0 - 5V):
    float voltage = sensorValue * (5.0 / 1024.0);
    float voltage1 = sensorValue1 * (5.0 / 1024.0);
    float voltage2 = sensorValue2 * (5.0 / 1024.0);
    //ADC end//////

  //mp3
  
int  root = 0;
  /* Initialize SD */
  while (!theSD.begin())
    {
      /* wait until SD card is mounted. */
      Serial.println("Insert SD card.");
    }

  // start audio system
  theAudio = AudioClass::getInstance();

  theAudio->begin(audio_attention_cb);

  puts("initialization Audio Library");

  /* Set clock mode to normal */
  theAudio->setRenderingClockMode(AS_CLKMODE_NORMAL);

  /* Set output device to speaker with first argument.
   * If you want to change the output device to I2S,
   * specify "AS_SETPLAYER_OUTPUTDEVICE_I2SOUTPUT" as an argument.
   * Set speaker driver mode to LineOut with second argument.
   * If you want to change the speaker driver mode to other,
   * specify "AS_SP_DRV_MODE_1DRIVER" or "AS_SP_DRV_MODE_2DRIVER" or "AS_SP_DRV_MODE_4DRIVER"
   * as an argument.
   */
  theAudio->setPlayerMode(AS_SETPLAYER_OUTPUTDEVICE_SPHP, AS_SP_DRV_MODE_LINEOUT);

  /*
   * Set main player to decode stereo mp3. Stream sample rate is set to "auto detect"
   * Search for MP3 decoder in "/mnt/sd0/BIN" directory
   */
  err_t err = theAudio->initPlayer(AudioClass::Player0, AS_CODECTYPE_MP3, "/mnt/sd0/BIN", AS_SAMPLINGRATE_AUTO, AS_CHANNEL_STEREO);

  /* Verify player initialize */
  if (err != AUDIOLIB_ECODE_OK)
    {
      printf("Player0 initialize error\n");
      exit(1);
    }

  /* Open file placed on SD card */
  if(root ==0){
  myFile = theSD.open("Sound.mp3");
  }
  else{
  myFile = theSD.open("Sound1.mp3");  
  }

  /* Verify file open */
  if (!myFile)
    {
      printf("File open error\n");
      exit(1);
    }
  printf("Open! 0x%08lx\n", (uint32_t)myFile);

  /* Send first frames to be decoded */
  err = theAudio->writeFrames(AudioClass::Player0, myFile);

  if ((err != AUDIOLIB_ECODE_OK) && (err != AUDIOLIB_ECODE_FILEEND))
    {
      printf("File Read Error! =%d\n",err);
      myFile.close();
      exit(1);
    }

  puts("Play!");

  /* Main volume set to -16.0 dB */
  //theAudio->setVolume(-160);
  //theAudio->startPlayer(AudioClass::Player0);

//mp3 end

//cam
  display.begin(); // 液晶ディスプレイの開始
  theCamera.begin(); // カメラの開始
  display.setRotation(3); // ディスプレイの向きを設定
//  theCamera.startStreaming(true, CamCB); // カメラのストリーミングを開始
  //cam end/////
}

void loop() {
  //ELTRES
  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) {
        // 送信直前通知時の処理
        event_send_ready = false;
        setup_payload_pow();
        // 送信ペイロードの設定
        EltresAddonBoard.set_payload(payload);
      }
      break;
     
    case PROGRAM_STS_STOPPED:
      // プログラム内部状態:終了
      break;
  }
  //ELTRES end //////

  //powsatus
  if(voltage < 1){
    powstatus = 0;
  }
  else{
    powstatus = 1;
  }

  //eht
  sensors_event_t humidity, temp;
  aht.getEvent(&humidity, &temp);// populate temp and humidity objects with fresh data
  
  Serial.print("Temperature: "); Serial.print(temp.temperature); Serial.println(" degrees C");
  
//24℃以上でサーキュレーターON
  if(temp.temperature > 24){
    if(FAN==0){
    Serial.println(" over 24 ");
    myFile = theSD.open("Sound0.mp3");  
    theAudio->setVolume(-160);
    theAudio->startPlayer(AudioClass::Player0);
    FAN=1;
    delay(4000);
    }
  }
  
//26℃以上でエアコンON
  if(temp.temperature > 25){
    if(AC==0){
    Serial.println(" over 25 ");    
    myFile = theSD.open("Sound1.mp3");  
    theAudio->setVolume(-160);
    theAudio->startPlayer(AudioClass::Player0);
    delay(4000);
    AC=1;
    }
  }
  
//26℃以上かつ、消費電力が多い場合にミストクーラON
  //消費電力使えないため、26℃以上でミストクーラON
  if(temp.temperature > 26){
    if(Mist==0){
    Serial.println(" over 26 ");   
    myFile = theSD.open("Sound2.mp3");  
    theAudio->setVolume(-160);
    theAudio->startPlayer(AudioClass::Player0);
    delay(4000);
    Mist=1}
  }
  if(temp.temperature < 20){
    if(FAN==1){
      Serial.println("FAN off");   
      myFile = theSD.open("Sound10.mp3");  
      theAudio->setVolume(-160);
      theAudio->startPlayer(AudioClass::Player0);
      FAN=0;
    }
    if(AC==1){
      Serial.println("AC off");   
      myFile = theSD.open("Sound11.mp3");  
      theAudio->setVolume(-160);
      theAudio->startPlayer(AudioClass::Player0);
      AC=0;
    }
      if(Mist==1){
      Serial.println("Mist off");   
      myFile = theSD.open("Sound12.mp3");  
      theAudio->setVolume(-160);
      theAudio->startPlayer(AudioClass::Player0);
      Mist=0;
    }
  }
  
//mp3 loop///////////////////////
puts("loop!!");

  /* Send new frames to decode in a loop until file ends */
  int err = theAudio->writeFrames(AudioClass::Player0, myFile);

  /*  Tell when player file ends */
  if (err == AUDIOLIB_ECODE_FILEEND)
    {
      printf("Main player File End!\n");
    }

  /* Show error code from player and stop */
  /*
  if (err)
    {
      printf("Main player error code: %d\n", err);
      goto stop_player;
    }

  if (ErrEnd)
    {
      printf("Error End\n");
      goto stop_player;
    }
    //mp3 loop end////////////////

  
//  Serial.print("Humidity: "); Serial.print(humidity.relative_humidity); Serial.println("% rH");

  delay(500);
}

//return;

//mp3 stop

stop_player:
  theAudio->stopPlayer(AudioClass::Player0);
  myFile.close();
  theAudio->setReadyMode();
  theAudio->end();
  exit(1);
*/
  
}
//mp3 stop end

//payload
void setup_payload_pow() {
  String lat_string = String((char*)last_gga_info.m_lat);
  String lon_string = String((char*)last_gga_info.m_lon);
  int index;
  uint32_t gnss_time;
  uint32_t utc_time;

  // GNSS時刻(epoch秒)の取得
  EltresAddonBoard.get_gnss_time(&gnss_time);
  // UTC時刻を計算(閏秒補正)
  utc_time = gnss_time - 18;

  // 設定情報をシリアルモニタへ出力
  Serial.print("[setup_payload_pow]");
  Serial.print("lat:");
  Serial.print(lat_string);
  Serial.print(",lon:");
  Serial.print(lon_string);
  Serial.print(",utc:");
  Serial.print(utc_time);
  Serial.print(",pos:");
  Serial.print(last_gga_info.m_pos_status);
  Serial.println();

  // ペイロード領域初期化
  memset(payload, 0x00, sizeof(payload));
  // ペイロード種別[FREEペイロード]設定
  payload[0] = 0x8A;
  // 電源状態
  index = 0;
  payload[1] = powstatus;
  //未使用
  index += 2;
  payload[2] = 0x00;
  index += 2;
  index += 1;   // skip "."
  //未使用
  payload[3] = 0x00;
  index += 2;
  //未使用
  payload[4] = 0x00;
  index = 0;
  //未使用
  payload[5] = 0x00;
  index += 1;
  //未使用
  payload[6] = 0x00;
  index += 2;
  //未使用
  payload[7] = 0x00;
  index += 2;
  index += 1;   // skip "."
  //未使用
 payload[8] = 0x00;
  index += 2;
  //未使用  
  payload[9] = 0x00;
  // 時刻(EPOCH秒)設定
  payload[10] = (uint8_t)((utc_time >> 24) & 0xff);
  payload[11] = (uint8_t)((utc_time >> 16) & 0xff);
  payload[12] = (uint8_t)((utc_time >> 8) & 0xff);
  payload[13] = (uint8_t)(utc_time & 0xff);
  // 拡張用領域(0固定)設定
  payload[14] = 0x00;
  // 品質設定
  payload[15] = last_gga_info.m_pos_status;
}

あとがき

当初はWi-SUN Add-onボード SPRESENSE-WiSUN-EVK-701も構成に入れて、自宅の使用電力に応した制御も追加しょうと考えいた。
しかし、ELTRESアドオンボードとSPRESENSE-WiSUN-EVK-701の両方がSPRESENSEメインボードのソケット部を使用するため干渉していしまい、両方同時に使用できないことがわかり構成から外しました。
ELTRASは最初にGPSを受信しないと通信できないため、屋内などGPSが入りづらいところではいざというときに使えない。
別売りの感度の良いアンテナや、線のながいアンテナに変えて窓に近くに設置する必要がある。

ログインしてコメントを投稿する