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

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が入りづらいところではいざというときに使えない。
別売りの感度の良いアンテナや、線のながいアンテナに変えて窓に近くに設置する必要がある。

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