室温に応じて自宅の機器を自動で動かしたい
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通信のエラー検出ペイロードを使用して、スマホに停電していることを知らせる。
<構成図>
・雨水タンク(中にポンプが入っており、ホーズがガーデンミストに接続している)
・ガーデンミスト
ソースコード
//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が入りづらいところではいざというときに使えない。
別売りの感度の良いアンテナや、線のながいアンテナに変えて窓に近くに設置する必要がある。
投稿者の人気記事
-
ramshink
さんが
2022/09/26
に
編集
をしました。
(メッセージ: 初版)
-
ramshink
さんが
2022/09/26
に
編集
をしました。
-
ramshink
さんが
2022/09/26
に
編集
をしました。
-
ramshink
さんが
2022/09/26
に
編集
をしました。
ログインしてコメントを投稿する