airpocketのアイコン画像
airpocket 2022年08月26日作成 (2022年09月26日更新) © MIT
製作品 製作品 閲覧数 916
airpocket 2022年08月26日作成 (2022年09月26日更新) © MIT 製作品 製作品 閲覧数 916

DIY可能な地すべり警報システム

DIY可能な地すべり警報システム

はじめに

日本は山岳地帯が国土の大半を占め、なおかつ降雨量が非常に多いという特徴を持っています。この地理的な要因により、毎年の台風シーズンには全国各地で大規模な土砂災害による被害が出ています。

多くの被害を出す土砂災害の一つに地すべりがあります。地すべりにはいくつかの予兆があると指摘されており、その中に、「段差・はらみ出し」「樹木の傾きや変化」について言及されています。地すべりの発生前には、わずかな地形の変化が発生しこの変化が種々の予兆現象として観測されています。この予兆現象を検出して事前の避難につなげられれば、人的被害を減少させることができそうです。
参考:農林水産省資料「地すべりの前兆現象の種類と観測方法

すでに多様な災害対策が行われているものの、広大な危険地域すべてを完全にカバーすることは困難なため、十分な対策を打てているとは言えません。今後もすべてのリスクをカバーすることはできないでしょう。
このプロジェクトでは、土砂災害の中でも多くの被害を出している地すべりの予兆を検知してアラートを発する装置を開発しました。

装置の特長

この装置はSpresenseを使用し、可能な限り入手しやすい部材を利用してDIY可能な警報システムを構築構築することを目標としています。山岳部や山すその傾斜地などに居住する方が、自宅の裏山や敷地内に簡易的に設置するという利用方法を想定しています。
既存の技術について調査したところ、同様のコンセプトの商品がすでに存在していることも判りました。しかしながら、どこで発生するか判らない予兆現象の検出を行うためには多数のデバイスを運用する必要があるため、端末価格のさらなる低減は重要な課題と言えます。
このプロジェクトでは、多機能であることよりもシンプルな操作性を、電子、電気の知識が乏しい方でもなるべく簡単に実装できる装置の開発を主眼としました。
筐体にはDaisoで購入できるガーデンライトを利用し、ショップで購入する事のできる部材の組み合わせで主要な機能を実装できます。

主な機能と特徴

・ガーデンライトを利用した筐体により、突き刺すだけの簡単設置
・加速度センサにより、装置の傾きの変化を検出
・ガーデンライトのLEDを利用してフラッシュによる警報表示
・Wi-SUN通信によるアラート信号の送信
・受信側デバイスからMQTTにて任意の端末へアラート情報を送信
・警報表示デバイスがアラート情報を受け取り警報表示
・ガーデンライトの太陽電池パネルによる電源の補完
・複数デバイスのデータをGoogleスプレッドシートに保存
・センサデバイスのステータスをスマホアプリ上にマップ付きで表示

追加予定の機能

・電源の強化と省電力化
 現在の電源では、一時間ごとの間欠駆動で約一週間しか稼働できないため、省電力化及び電源容量の拡張が必要。
・GNSS外部アンテナ化
 GNSSによる設置位置取得機能についてはオンボードアンテナではデータ取得に難あり、外部アンテナ引き出しが必要。現在は位置情報は手動入力。

稼働している様子

フィールドテストの様子です。

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

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

デバイスのステータス表示アプリの様子です。表示にはダミーデータを使用しています。

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

データを保存するスプレッドシート。
キャプションを入力できます

設置時

装置を設置する際には、監視したい場所に装置を挿して自立させ、電源を入れるだけです。(0:04~)
電源を入れると加速度センサで装置の傾きを測定し初期状態を記憶、Wi-SUN通信で起動完了ステータスを送信します。
システム起動時に警報表示用のLEDが一回フラッシュ、Wi-SUN通信成功後に二回フラッシュ正常起動を確認できます。(0:14~)
起動が完了するとDeepsleepに入ります。

異常未検出時

初期設定では、1時間ごとにDeepsleepを解除し傾きを測定します。上記の動画では、動作確認のためDeepsleepは3秒に設定しています。初期の傾きとの差が閾値(初期設定では5°)未満であれば、異常なしのステータスと角度変化の値を送信して再度Deepsleepに入ります。

異常検出時

Deepsleep解除後の装置の傾きと初期の傾きの差が閾値以上であれば、ガーデンライトのLEDがフラッシュし、Wi-SUN通信にてアラートのステータスを送信します。

受信側デバイス(Raspberry Pi 4B)

受信側デバイスにはRaspberry Pi 4Bを用いました。Wi-SUN通信の受信には、Wi-SUN USBドングル BP35C2を使用しています。
MQTTサーバを兼ねており、警報表示デバイスや任意のデバイスに警報データを送信します。

警報表示デバイス(M5Stack)

受信側デバイスから警報データを受信すると、異常を検知したセンサー端末のIDを表示、テキスト、背景色、警報音で警報を表示します。(0:45~)

スマホアプリ(Glide)

Raspberry Pi で受信した情報は、Googleスプレッドシートに保存されます。
保存された内容は、Glideで制作したスマホアプリで閲覧可能です。スマホアプリにはセンサー端末のリストとそれぞれのステータス、GNSS情報を元に設置位置を地図上に表示します。

システム構成

全体のシステム構成は次の通りです。
システム構成図

センサー部のコントローラーにはSpresenseを使用し、BMI160の加速度センサの値からデバイスの傾きを計算します。
測定結果は、Wi-SUN通信で受信装置(Raspberry Pi)へ送信しています。
受信装置はpythonでWi-SUN通信やデータの処理を行い、Node-REDでMQTTサーバを稼働、ディスプレイ装置に警報情報を送信します。
受信したデータはGoogleスプレッドシートに送信、記録し、Google Apps Scriptで複数センサの最新データの記録、更新おを行います。
センサデバイスのステータスや設置位置はGlideにより作成したスマホアプリで、最新データを確認できます。設置位置も地図上に表示できるため、設置場所の確認やセンサの回収も容易にできます。
警報ディスプレイ装置にはM5Stackを使用し、アラート発生時には音と画面表示で警報を表示します。複数のディスプレイ装置を同時使用できるため、居間のテレビ横、寝室のベッドサイド、子供部屋など 任意の位置に増設できます。

部品リスト

回路図(LED点灯用基板)

この基板はLED点灯機能を実装する場合に必要ですが、アラートを無線出力のみで行う場合は必要ありません。
キャプションを入力できます

プログラム

このシステムには、Spresenseを使用したセンサ装置のほかにRaspberry Piを使用した受信装置とM5Stackを使用したディスプレイ装置が含まれます。それぞれで使用するプログラムは次の通りです。

Spresense

メインスケッチ

#include <Wire.h>
#include <math.h>
#include <BMI160Gen.h>
#include <LowPower.h>
#include <RTC.h>
#include <Arduino.h>
#include <File.h>
#include <Flash.h>
#include "bp35c0-j11.h"

#define BAUDRATE  115200  //115200
#define SENSE_RATE   200
#define GYRO_RANGE   250
#define ACCL_RANGE     2
#define SLEEPTIME      3

#define COUNT_FOR_CALIB 1024

#define deg_to_rad(a) (a/180*M_PI)
#define rad_to_deg(a) (a/M_PI*180)

#define PRINT_ACCL


uint16_t adjust_usec;
static float calib_accel_x = 0, calib_accel_y = 0, calib_accel_z = 0;
volatile int alarmInt      = 0;
const int LEDPIN           = 23;                //LED pin number for alert
const double threthold     = 5.0;        //***Threshold for alerting (unit:degree)*******
unsigned char state = 0;
char machineID = "ID:LSA0001";

BP35C0J11 bp35c0j11;

char * dtostrf(double value, unsigned int width, unsigned int decimalPlaces, char* buf)
{
  char fmt[20];

  snprintf(fmt, 20, "%%%d.%df", width, decimalPlaces);
  sprintf(buf, fmt, value);
  return buf;
}

void printClockMode()
{
  clockmode_e mode = LowPower.getClockMode();

  Serial.println("--------------------------------------------------");
  Serial.print("clock mode: ");
  switch (mode) {
  case CLOCK_MODE_156MHz: Serial.println("156MHz"); break;
  case CLOCK_MODE_32MHz:  Serial.println("32MHz"); break;
  case CLOCK_MODE_8MHz:   Serial.println("8MHz"); break;
  }
}

float convertRawAccel(int aRaw) 
{
  // ex) if the range is +/-2g ; +/-32768/2 = +/-16384 LSB/g
  float lsb_g = float(0x7FFF) / ACCL_RANGE;
  return aRaw / lsb_g;
}


void setup() {
  Serial.begin(BAUDRATE);
  RTC.begin();
  LowPower.begin();
  LowPower.clockMode(CLOCK_MODE_32MHz); //156,32,8
//  printClockMode();
  bootcause_e bc = LowPower.bootCause();
  Serial.print("bc = ");
  Serial.println(bc);
  pinMode(LEDPIN, OUTPUT);
  digitalWrite(LEDPIN, LOW);

  digitalWrite(LED0,LOW);
  digitalWrite(LED1,LOW);
  digitalWrite(LED2,LOW);
  digitalWrite(LED3,LOW);

  if (bc == 0){
    digitalWrite(LEDPIN,HIGH);
    digitalWrite(LED3,HIGH);
    delay(10);
    digitalWrite(LEDPIN,LOW);
    digitalWrite(LED3,LOW);
  }
  boolean rc = FALSE ;
  bp35c0j11.j11_init();
  rc = bp35c0j11.wait_msg();
  if(rc == TRUE){
    state = 1 ;           //  hardware reset end 
  }else{
    state = 0 ;
  }



  while(state < 8){
    delay(500);
    unsigned char msg_length = 0 ;
    boolean rc = 0 ;
    Serial.print("State = ");
    Serial.println(state, DEC);
    delay(500);
    switch (state) {
      case(0):   // need hardware reset 
        rc = bp35c0j11.cmd_send(CMD_RESET);
        rc = bp35c0j11.wait_msg();
        if(rc == TRUE){
          state = 1 ;
        }
        break;
      case(1):  //  init state 
        rc = bp35c0j11.cmd_send(CMD_INI);
        rc = bp35c0j11.wait_msg();
        if(rc == TRUE){
          state = 2;
        }
        break;
      case(2): // HAN PANA setting
        rc = bp35c0j11.cmd_send(CMD_PANA_SET);
        rc = bp35c0j11.wait_msg();
        if(rc == TRUE){
          state = 4;
        }
        break;
      case(3): // active scan
        rc = bp35c0j11.cmd_send(CMD_SCAN);
        rc = bp35c0j11.wait_msg();
        if(rc == TRUE){
          rc = bp35c0j11.wait_msg();
          if(rc == TRUE){
            state = 4;
          }
        }
        break;
      case(4): //  HAN act
        rc = bp35c0j11.cmd_send(CMD_HAN);
        rc = bp35c0j11.wait_msg();
        if(rc == TRUE){
          state = 5;
        }
        break;
      case(5): // HAN PANA act
        rc = bp35c0j11.cmd_send(CMD_PANA);
        rc = bp35c0j11.wait_msg();
        if(rc == TRUE){
          rc = bp35c0j11.wait_msg();
          if(rc == TRUE){
            state = 7;
          }
        }
        break;
      case(6): // rcv mode change
        rc = bp35c0j11.cmd_send(CMD_CON_SET);
        rc = bp35c0j11.wait_msg();
        if(rc == TRUE){
          state = 7;
        }
        break;
      case(7): // my_port open
        rc = bp35c0j11.cmd_send(CMD_PORTOPEN);
        rc = bp35c0j11.wait_msg();
        if(rc == TRUE){
          state = 8;
          
        }
        break;
    }
  }


  if (bc == 0){
    digitalWrite(LEDPIN,HIGH);
    digitalWrite(LED3,HIGH);
    delay(10);
    digitalWrite(LEDPIN,LOW);
    digitalWrite(LED3,LOW);
    delay(300);
    digitalWrite(LEDPIN,HIGH);
    digitalWrite(LED3,HIGH);
    delay(10);
    digitalWrite(LEDPIN,LOW);
    digitalWrite(LED3,LOW);
  }
  
  BMI160.begin(BMI160GenClass::I2C_MODE);
  BMI160.setAccelerometerRate(SENSE_RATE);
  BMI160.setAccelerometerRange(ACCL_RANGE);

  // Serial.println("Calibrating...");
  unsigned long sampling_rate = 1000 / SENSE_RATE;

  // Calcurate offset
  for (int i = 0; i < COUNT_FOR_CALIB; ++i) {
    int xAcc, yAcc, zAcc;
    BMI160.readAccelerometer(xAcc, yAcc, zAcc);
    calib_accel_x += convertRawAccel(xAcc);
    calib_accel_y += convertRawAccel(yAcc);
    calib_accel_z += convertRawAccel(zAcc);
  }
  
  // mean value
  calib_accel_x /= COUNT_FOR_CALIB;
  calib_accel_y /= COUNT_FOR_CALIB;
  calib_accel_z /= COUNT_FOR_CALIB;
  double xInit = calib_accel_x, yInit = calib_accel_y, zInit = calib_accel_z;
  
  // Processing at initial startup
  if (bc == 0){
    if (Flash.exists("initData.txt")) Flash.remove("initData.txt");
    File initData = Flash.open("initData.txt",FILE_WRITE);
    initData.print(String(calib_accel_x, 3));
    initData.print(",");
    initData.print(String(calib_accel_y, 3));
    initData.print(",");
    initData.print(String(calib_accel_z, 3));
    initData.print(",");
    initData.close();
    bp35c0j11.radiodata = "ID:LSA0001";
    rc = bp35c0j11.cmd_send(CMD_UDPSEND);
    rc = bp35c0j11.wait_msg();

    bp35c0j11.radiodata = "Latitude:111.111111"; //dummy data
    rc = bp35c0j11.cmd_send(CMD_UDPSEND);
    rc = bp35c0j11.wait_msg();

    bp35c0j11.radiodata = "Longitude:222.222222"; //dummy data
    rc = bp35c0j11.cmd_send(CMD_UDPSEND);
    rc = bp35c0j11.wait_msg();

    bp35c0j11.radiodata = "STATUS:launch";
    rc = bp35c0j11.cmd_send(CMD_UDPSEND);
    rc = bp35c0j11.wait_msg();  
 
  }

  //Processing after deep sleep
  int counter = 0;
  if (bc == 5){
    String str;
    File initData = Flash.open("initData.txt");  
    while(initData.available()){
      if (counter == 0) {
        xInit = initData.readStringUntil(',').toDouble();
      }else if (counter == 1){
        yInit = initData.readStringUntil(',').toDouble();
      }else if (counter == 2){
        zInit = initData.readStringUntil(',').toDouble();
      }else{
        str += char(initData.read());
      }
      counter++;
    }
    initData.close();
  }

  // SENSE_RATE is Hz and 2 millsec is the process time for this sketch
  adjust_usec = (1000/SENSE_RATE - 2) * 1000; 

  // Calculate the angle between the initial angle and the current angle
  double numerator = (xInit * calib_accel_x) + (yInit * calib_accel_y) + (zInit * calib_accel_z);
  double denominator1 = sqrt(pow(abs(xInit),2) + pow(abs(yInit),2) + pow(abs(zInit),2));
  double denominator2 = sqrt(pow(abs(calib_accel_x),2) + pow(abs(calib_accel_y),2) + pow(abs(calib_accel_z),2));
  double degree = acos(numerator/(denominator1*denominator2))*(180/3.141592654);
//  Serial.println(degree);

  if (bc == 5){
    bp35c0j11.radiodata = "ID:LSA0001";
    rc = bp35c0j11.cmd_send(CMD_UDPSEND);
    rc = bp35c0j11.wait_msg();

  //alart output setting
    if (degree >= threthold){

      bp35c0j11.radiodata = "DEGREE:";
      char chardegree[7];
      dtostrf(degree,3,3,chardegree);
      strcat(bp35c0j11.radiodata, chardegree);
      rc = bp35c0j11.cmd_send(CMD_UDPSEND);
      rc = bp35c0j11.wait_msg();

      Serial.println("alert send");
      bp35c0j11.radiodata = "STATUS:alert";
      rc = bp35c0j11.cmd_send(CMD_UDPSEND);
      rc = bp35c0j11.wait_msg();  

      while(true){
        digitalWrite(LEDPIN,HIGH);
        digitalWrite(LED3,HIGH);
        delay(10);
        digitalWrite(LEDPIN,LOW);
        digitalWrite(LED3,LOW);
        delay(1000);
      }
    }
    else {
      bp35c0j11.radiodata = "DEGREE:";
      char chardegree[7];
      dtostrf(degree,3,3,chardegree);
      strcat(bp35c0j11.radiodata, chardegree);
      rc = bp35c0j11.cmd_send(CMD_UDPSEND);
      rc = bp35c0j11.wait_msg();  

      bp35c0j11.radiodata = "STATUS:in range";
      rc = bp35c0j11.cmd_send(CMD_UDPSEND);
      rc = bp35c0j11.wait_msg();    
    }
  }
  LowPower.deepSleep(SLEEPTIME);
}

void loop() {

}

Wi-SUNライブラリ

Wi-SUN通信用のライブラリは、Rohm さんのライブラリを参考に一部変更しています。
Rohmさんのライブラリはこちら
変更点の詳細説明記事はこちら

bp35c0-j11.h

/*
  bp35c0-j11.h
 Copyright (c) 2019 ROHM Co.,Ltd.

 Permission is hereby granted, free of charge, to any person obtaining a copy
 of this software and associated documentation files (the "Software"), to deal
 in the Software without restriction, including without limitation the rights
 to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 copies of the Software, and to permit persons to whom the Software is
 furnished to do so, subject to the following conditions:

 The above copyright notice and this permission notice shall be included in
 all copies or substantial portions of the Software.

 THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
 THE SOFTWARE.
*/
#ifndef _BP35C0J11_H_
#define _BP35C0J11_H_

#define DEBUG


#define CMD_HDR_LEN  ((unsigned char)8)
#define UNI_CODE_LEN ((unsigned char)4)

#define CMD_RESET    (0x00D9)
#define CMD_INI      (0x005F)
#define CMD_HAN      (0x000A)
#define CMD_PANA     (0x003A)
#define CMD_PANA_SET (0x002C)
#define CMD_CON_SET  (0x0025)
#define CMD_UDPSEND  (0x0008)
#define CMD_SCAN     (0x0051)
#define CMD_PORTOPEN (0x0005)

#define NORT_WAKE    (0x6019)
#define RES_INI      (0x205F)
#define RES_HAN      (0x200A)
#define RES_PANA     (0x203A)
#define RES_PANA_SET (0x202C)
#define RES_CON_SET  (0x2025)
#define RES_UDPSEND  (0x2008)
#define RES_SCAN     (0x2051)
#define NORT_SCAN    (0x4051)
#define RES_PORTOPEN (0x2005)
#define NORT_PANA    (0x6028)

#define TIMEOUT      ((unsigned short)10000)

#define PIN_ENABLE  (PIN_D20)    // level shifter enable pin
#define PIN_RESET   (PIN_D21)    // wisun module reset pin

#define TRUE 1
#define FALSE 0

typedef struct {
  unsigned char uni_code[4];
  unsigned char cmd_code[2];
  unsigned char msg_len[2];
  unsigned char hdr_chksum[2];
  unsigned char dat_chksum[2];
  unsigned char data[128];
}CMD_FORMAT;

class BP35C0J11
{
  public:
    char* radiodata = "test2";
    BP35C0J11(void);
    void j11_init(void);
    boolean wait_msg(void);
    boolean cmd_send(unsigned short cmd);
    void static msg_create(unsigned short cmd , unsigned short msg_length ,unsigned short hdr_chksum , unsigned short dat_chksum, unsigned char *pdata , unsigned char *psend_data );
  private:
    void static debugmsg(unsigned short datalength , unsigned char *psend_data);
};
#endif    //_BP35C0J11_H_

bP35c0-j11.cpp

/********************************************************************************
  bp35c0-j11.cpp
  Copyright (c) 2019 ROHM Co.,Ltd.

  Permission is hereby granted, free of charge, to any person obtaining a copy
  of this software and associated documentation files (the "Software"), to deal
  in the Software without restriction, including without limitation the rights
  to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
  copies of the Software, and to permit persons to whom the Software is
  furnished to do so, subject to the following conditions:

  The above copyright notice and this permission notice shall be included in
  all copies or substantial portions of the Software.

  THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
  IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
  FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
  AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
  LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
  OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
  THE SOFTWARE.
*********************************************************************************/
#include <Arduino.h>
#include "bp35c0-j11.h"

unsigned const char uni_req[4] = {0xD0 , 0xEA , 0x83 , 0xFC};
unsigned const char uni_res[4] = {0xD0 , 0xF9 , 0xEE , 0x5D};

unsigned const char ini_data[4] = {0x03 , 0x00 , 0x05 , 0x00};       // エンドデバイス/Sleep 非対応/922.9MHz/20mW出力
unsigned const char pair_id[8] = {0x00 , 0x1D , 0x12 , 0x91 , 0x00 , 0x02 , 0x2E , 0x8B};   // 接続先MACアドレス
//board mac address 001D12910003584C
//USB   mac address 001D129100022E8B
unsigned const char mac_adr[16] = {0xFE , 0x80 , 0x00 , 0x00 , 0x00 , 0x00 , 0x00 , 0x00 , 0x02 , 0x1D , 0x12 , 0x91 , 0x00 , 0x02 , 0x2E , 0x8B}; // 接続先IPv6アドレス
unsigned const char my_port[2] = { 0x01 , 0x23 };     // オープンするUDPポート
unsigned const char dist_port[2] = { 0x0E , 0x1A };   // 送信先UDPポート
unsigned const char password[16] = { '1' , '1' , '1' , '1' , '2', '2' , '2' , '2' , '3' , '3' , '3' , '3' , '4' , '4' , '4' , '4' };    // PANA認証時のパスワード
//unsigned const char radiodata[4] = { 'T' , 'E' , 'S' , 'T'};         // 送信データ
//char radiodata[] = "test2";


CMD_FORMAT cmd_format;

BP35C0J11::BP35C0J11(void)
{
  
}

/********************************************************************************
*   Name     : j11_init
*   Function : initial setting bp35c0-j11
*   input    : -
*   return   : -
*********************************************************************************/
void BP35C0J11::j11_init(void) {

  // configure output D20/D21
  pinMode(PIN_ENABLE, OUTPUT);      
  pinMode(PIN_RESET, OUTPUT);
  digitalWrite(PIN_ENABLE, HIGH);


  delay(1000);

  // Serial port initial 
  Serial2.begin(115200); //115200
  Serial.begin(115200);  //115200
  Serial.write("RESET");
  Serial.println("");

  digitalWrite(PIN_RESET, LOW);     // reset
  delay(500);
  digitalWrite(PIN_RESET, HIGH);

}

/********************************************************************************
*   Name     : wait_msg
*   Function : wait for response from bp35c0-j11
*   input    : -
*   return   : TRUE/FALSE
*********************************************************************************/
boolean BP35C0J11::wait_msg(void)
{
  unsigned long start_time;
  unsigned long current_time;
  unsigned char rcvdata[128] = {0} ;
  unsigned char cnt = 0 ;
  start_time = millis();
  while (Serial2.available() == 0)
  {
    current_time = millis();
    if ((current_time - start_time) > TIMEOUT) {
      Serial.println("receive timeout");
      return FALSE;
    }

  }
  while (Serial2.available() > 0 ) {
    delay(5);
    rcvdata[cnt] = Serial2.read();
#ifdef DEBUG
    Serial.print(rcvdata[cnt] , HEX);
#endif
    cnt++;
    if (cnt >= 128) {
      Serial.println("receive data over flow");
      return FALSE;
    }
  }
  if (rcvdata[0] == uni_res[0] && rcvdata[1] == uni_res[1] &&
      rcvdata[2] == uni_res[2] && rcvdata[3] == uni_res[3]) {     // RESPONSE/NORTIFICATION
    switch (rcvdata[4] << 8 | rcvdata[5]) {
      case (NORT_WAKE):

        break;
      case (RES_INI):
        if (rcvdata[12] == 0x01) {
          Serial.println("Init Success");
        } else {
          Serial.println("Init Error");
          return FALSE;
        }
        break;
      case (RES_PANA_SET):
        if (rcvdata[12] == 0x01) {
          Serial.println("PANA Password set Success");
        } else {
          Serial.println("PANA Password set Error");
          return FALSE;
        }
        break;
      case (RES_SCAN):

        break;
      case (NORT_SCAN):
        break;
      case (RES_HAN):
        if (rcvdata[12] == 0x01) {
          Serial.println("HAN Act Success");
        } else {
          Serial.println("HAN Act Error");
          return FALSE;
        }
        break;
      case (RES_PANA):
        if (rcvdata[12] == 0x01) {
          Serial.println("PANA Act Success");
        } else {
          Serial.println("PANA Act Error");
          return FALSE;
        }
        break;
      case (NORT_PANA):
        if (rcvdata[12] == 0x01) {
          Serial.println("PANA Connect Success");
        } else {
          Serial.println("PANA Connecgt Error");
          return FALSE;
        }
        break;
      case (RES_CON_SET):
        if (rcvdata[12] == 0x01) {
          Serial.println("Normal connect mode");
        } else {
          Serial.println("connect mode change error");
          return FALSE;
        }
        break;
      case (RES_PORTOPEN):
        if (rcvdata[12] == 0x01) {
          Serial.println("UDP port open Success");
        } else {
          Serial.println("UDP port open Error");
          return FALSE;
        }
        break;
      case (RES_UDPSEND):
        if (rcvdata[12] == 0x01) {
          Serial.println("UDP send Success");
        } else {
          Serial.println("UDP send Error");
          return FALSE;
        }
        break;
      case (0x2FFF):
        Serial.println("checksum error");
        return FALSE;
        break;
      default:
        Serial.println("uni code error");
        return FALSE;
        break;
    }

  } else {
    Serial.println("recv data error");
    return FALSE;
  }

  return TRUE;
}



/********************************************************************************
*   Name     : cmd_send
*   Function : REQUEST command to bp35c0-j11
*   input    : cmd  - REQUEST command 
*   return   : TRUE/FALSE
*********************************************************************************/
boolean BP35C0J11::cmd_send(unsigned short cmd) {
  unsigned short hdr_chksum = uni_req[0] + uni_req[1] + uni_req[2] + uni_req[3] ;
  unsigned short dat_chksum = 0 ;
  unsigned short msg_length = 0 ;
  unsigned short dat_length = 0 ;
  unsigned short send_dat_size = 0 ;
  unsigned char data[128] = {0};

  unsigned char send_data[128] = {0} ;
  unsigned char cnt = 0 ;

  switch (cmd) {
    case (CMD_RESET):
      dat_length = 0;
      msg_length = (unsigned short)(4 + dat_length);
      hdr_chksum += CMD_RESET + msg_length;
      dat_chksum = 0 ;
      msg_create(CMD_RESET , msg_length , hdr_chksum , dat_chksum, data , send_data );
      Serial2.write(send_data, (msg_length + CMD_HDR_LEN));
#ifdef DEBUG
      debugmsg( msg_length + CMD_HDR_LEN , send_data);
#endif

      break;
    case (CMD_INI):
      dat_length = (unsigned short)4;
      msg_length = (unsigned short )( 4 + dat_length);
      hdr_chksum += CMD_INI + msg_length ;
      for (cnt = 0 ; cnt < dat_length ; cnt++ ) {
        data[cnt] = ini_data[cnt] ;
      }
      for (cnt = 0 ; cnt < dat_length ; cnt++) {
        dat_chksum += data[cnt];
      }
      msg_create(CMD_INI , msg_length , hdr_chksum , dat_chksum, data , send_data );
      Serial2.write(send_data, (msg_length + CMD_HDR_LEN));
#ifdef DEBUG
      debugmsg( msg_length + CMD_HDR_LEN , send_data);
#endif
      break;
    case (CMD_PANA_SET):
      dat_length = (unsigned short)16 ;
      msg_length = (unsigned short)(4 + dat_length);
      hdr_chksum += CMD_PANA_SET + msg_length;
      for (cnt = 0 ; cnt < dat_length ; cnt++ ) {
        data[cnt] = password[cnt] ;
      }
      for (cnt = 0 ; cnt < dat_length ; cnt++) {
        dat_chksum += data[cnt];
      }
      msg_create(CMD_PANA_SET , msg_length , hdr_chksum , dat_chksum, data , send_data );
      Serial2.write(send_data, (msg_length + CMD_HDR_LEN));
#ifdef DEBUG
      debugmsg( msg_length + CMD_HDR_LEN , send_data);
#endif

      break;
    case (CMD_SCAN):
      break;
    case (CMD_HAN):
      dat_length = (unsigned short)8 ;
      msg_length = (unsigned short)(4 + dat_length);
      hdr_chksum += CMD_HAN + msg_length;
      for (cnt = 0 ; cnt < dat_length ; cnt++ ) {
        data[cnt] = pair_id[cnt] ;
      }
      for (cnt = 0 ; cnt < dat_length ; cnt++) {
        dat_chksum += data[cnt];
      }
      msg_create(CMD_HAN , msg_length , hdr_chksum , dat_chksum, data , send_data );
      Serial2.write(send_data, (msg_length + CMD_HDR_LEN));
#ifdef DEBUG
      debugmsg( msg_length + CMD_HDR_LEN , send_data);
#endif


      break;
    case (CMD_PANA):
      dat_length = 0;
      msg_length = (unsigned short)(4 + dat_length);
      hdr_chksum += CMD_PANA + msg_length;
      dat_chksum = 0 ;
      msg_create(CMD_PANA , msg_length , hdr_chksum , dat_chksum, data , send_data );
      Serial2.write(send_data, msg_length + CMD_HDR_LEN);
#ifdef DEBUG
      debugmsg( msg_length + CMD_HDR_LEN , send_data);
#endif
      break;
    case (CMD_CON_SET):
      dat_length = 1;
      msg_length = (unsigned short)(4 + dat_length);
      hdr_chksum += CMD_CON_SET + msg_length;
      data[0] = 0x02 ;
      dat_chksum = data[0] ;
      msg_create(CMD_CON_SET , msg_length , hdr_chksum , dat_chksum, data , send_data );
      Serial2.write(send_data, msg_length + CMD_HDR_LEN);
#ifdef DEBUG
      debugmsg( msg_length + CMD_HDR_LEN , send_data);
#endif

      break;
    case (CMD_PORTOPEN):
      dat_length = 2;
      msg_length = (unsigned short)(4 + dat_length);
      hdr_chksum += CMD_PORTOPEN + msg_length;
      for (cnt = 0 ; cnt < dat_length ; cnt++ ) {
        data[cnt] = my_port[cnt] ;
      }
      for (cnt = 0 ; cnt < dat_length ; cnt++) {
        dat_chksum += data[cnt];
      }
      msg_create(CMD_PORTOPEN , msg_length , hdr_chksum , dat_chksum, data , send_data );
      Serial2.write(send_data, msg_length + CMD_HDR_LEN);
#ifdef DEBUG
      debugmsg( msg_length + CMD_HDR_LEN , send_data);
#endif
      break;
    case (CMD_UDPSEND):

      //radiodata = "test45";
      send_dat_size = strlen(radiodata);
      Serial.println(send_dat_size);
      for (int i = 0; i < send_dat_size; i++){
        Serial.printf(radiodata[i]);
      }
      Serial.println(radiodata[0]);
      dat_length = 22 + send_dat_size ;
      msg_length = (unsigned short)(4 + dat_length);
      hdr_chksum += CMD_UDPSEND + msg_length;
      for (cnt = 0 ; cnt < 16 ; cnt++ ) {
        data[cnt] = mac_adr[cnt] ;
      }
      data[16] = my_port[0] ;
      data[17] = my_port[1] ;          // 送信元UDPポート :0x0123
      data[18] = dist_port[0] ;
      data[19] = dist_port[1] ;        // 送信先UDPポート:0x0E1A
      data[20] = (unsigned char)(send_dat_size >> 8);
      data[21] = (unsigned char)(send_dat_size & 0xFF); // send data length
      for (cnt = 0 ; cnt < send_dat_size ; cnt++) {
        data[22 + cnt] = radiodata[cnt];              // data
      }
      for (cnt = 0 ; cnt < dat_length ; cnt++) {
        dat_chksum += data[cnt];
      }
      msg_create(CMD_UDPSEND , msg_length , hdr_chksum , dat_chksum, data , send_data );
      Serial2.write(send_data, msg_length + CMD_HDR_LEN);
#ifdef DEBUG
      debugmsg( msg_length + CMD_HDR_LEN , send_data);
#endif
      break;
    default:

      break;
  }

  return TRUE;
}

/********************************************************************************
*   Name     : msg_create
*   Function : create Request command format
*   input    : cmd - Request command
*              msg_length - message data length
*              hdr_chksum - header checksum
               dat_chksum - data checksum
               *pdada     - wireless data
               *psend_data- request command format data
*   return   : -
*********************************************************************************/
void static BP35C0J11::msg_create(unsigned short cmd , unsigned short msg_length , unsigned short hdr_chksum , unsigned short dat_chksum, unsigned char *pdata , unsigned char *psend_data )
{
  unsigned char cnt = 0 ;

  for (cnt = 0 ; cnt < 4 ; cnt++) {
    psend_data[cnt] = uni_req[cnt];
  }
  psend_data[4] = (unsigned char)((cmd & 0xFF00) >> 8);
  psend_data[5] = (unsigned char)(cmd & 0xFF);
  psend_data[6] = (unsigned char)((msg_length & 0xFF00) >> 8);
  psend_data[7] = (unsigned char)(msg_length & 0xFF);
  psend_data[8] = (unsigned char)((hdr_chksum & 0xFF00) >> 8);
  psend_data[9] = (unsigned char)(hdr_chksum & 0xFF);
  psend_data[10] = (unsigned char)((dat_chksum & 0xFF00) >> 8);
  psend_data[11] = (unsigned char)(dat_chksum & 0xFF);

  if (msg_length > 4) {
    for (cnt = 0 ; cnt < msg_length - 4 ; cnt++)
    {
      psend_data[12 + cnt] = pdata[cnt];
    }
  }
}



/********************************************************************************
*   Name     : debugmsg
*   Function : output serial console for debug
*   input    : datalength - output data lengh
               psend_data - output data pointer
*   return   : -
*********************************************************************************/
void BP35C0J11::debugmsg(unsigned short datalength , unsigned char* psend_data) {
  unsigned char cnt = 0 ;

  for ( cnt = 0 ; cnt < datalength ; cnt++) {
    Serial.print(psend_data[cnt] , HEX);
    Serial.print(" ");
  }
  Serial.println("");
}

Raspberry Pi (Wi-SUN受信)

Wi-SUNの受信はRPi上のPythonで動かしています。受信結果は、同じくRPi上で動かしているMQTTサーバへ送信しています。
Wi-SUN通信部分のプログラムはデバイスプラスのこちらの記事を参考にして一部を変更の上利用させていただきました。
MQTT通信部分のプログラムはこちらの記事を参考にして利用させていただきました。

Wi-SUN Dongle初期設定

Wi-SUN通信用のUSBドングルを利用する際には、このプログラムで初期化する必要があります。
この初期化は、Raspberry Piを起動する都度必要です。Raspberry Pi稼働中にUSBドングルを抜き差しすると、ポートが変化する為注意してください。

# /usr/bin/env python3
# -*- coding: utf-8 -*-
 
import serial
from time import sleep
 
con=serial.Serial('/dev/ttyUSB0',115200)
print(con.portstr)
 
def sendcmd(cmd_str):
    con.write(cmd_str.encode())
    while True:
        str_bf=con.readline()
        if str_bf !="":
            print(str_bf)
            if str_bf == b'OK\r\n':
                break
    sleep(0.5)
 
sendcmd('SKSREG SF0 1\r\n')
sendcmd('SKSREG S2 23\r\n')
sendcmd('SKSREG S3 5678\r\n')
sendcmd('SKSREG SA9 1\r\n')
sendcmd('SKSREG SA2 1\r\n')
sendcmd('SKSTART\r\n')
sendcmd('SKSETHPWD 001D12910003584C 1111222233334444\r\n')
sendcmd('SKINFO')
con.close()

W-SUN受信及びMQTT送信

#!/usr/bin/env python
# -*- coding: utf-8 -*-
#Wi-SUN Rx program
import serial
import datetime
import paho.mqtt.client as mqtt     # MQTTのライブラリをインポート

con=serial.Serial('/dev/ttyUSB0',115200)
print(con.portstr)
status =""
checker = 0
alert = False
latitude = 1.1
longitude = 2.2
# ブローカーに接続できたときの処理
def on_connect(client, userdata, flag, rc):
  print("Connected with result code " + str(rc))

# ブローカーが切断したときの処理
def on_disconnect(client, userdata, rc):
  if rc != 0:
     print("Unexpected disconnection.")

# publishが完了したときの処理
def on_publish(client, userdata, mid):
  print("publish: {0}".format(mid))

client = mqtt.Client()                 # クラスのインスタンス(実体)の作成
client.on_connect = on_connect         # 接続時のコールバック関数を登録
client.on_disconnect = on_disconnect   # 切断時のコールバックを登録
client.on_publish = on_publish         # メッセージ送信時のコールバック

client.connect("localhost", 1883, 60)  # 接続先は自分自身
 
  # 通信処理スタート
client.loop_start()    # subはloop_forever()だが,pubはloop_start()で起動だけさせる

while True:
    str_bf=con.readline()
    if str_bf !="":
        index = str_bf.find(b'ID:')
        if index !=-1:
            dt=datetime.datetime.now()
            print("")
            print(dt)
            index_end = str_bf.find(b'\x00')
            datastr = str_bf[index+3:index_end].decode()
            env_data = datastr.split(',')           
            Testmessage = ""
            sensorID = "".join(env_data)[3:-1]
            print("ID=",sensorID)
        
        index = str_bf.find(b'GNSS:')
        if index !=-1:
            index_end = str_bf.find(b'\x00')
            datastr = str_bf[index+5:index_end].decode()
            env_data = datastr.split(',')            
            gnss = "".join(env_data)[:-1]
            print("GNSS=",gnss)
        index = str_bf.find(b'Latitude:')
        if index !=-1:
            index_end = str_bf.find(b'\x00')
            datastr = str_bf[index+9:index_end].decode()
            env_data = datastr.split(',')            
            latitude = "".join(env_data)[:-1]
            print("Latitude=",latitude)
        index = str_bf.find(b'Longitude:')
        if index !=-1:
            index_end = str_bf.find(b'\x00')
            datastr = str_bf[index+10:index_end].decode()
            env_data = datastr.split(',')            
            longitude = "".join(env_data)[:-1]
            print("Longitude=",longitude)          
        index = str_bf.find(b'DEGREE:')
        if index !=-1:
            index_end = str_bf.find(b'\x00')
            datastr = str_bf[index+7:index_end].decode()
            env_data = datastr.split(',')            
            degree = "".join(env_data)[:-1]
            print("DEGREE=",degree)
        index = str_bf.find(b'STATUS:')
        if index !=-1:
            index_end = str_bf.find(b'\x00')
            datastr = str_bf[index+7:index_end].decode()
            env_data = datastr.split(',')            
            status = "".join(env_data)[:-1]
            print("STATUS=",status)
            
            if status == "launch":
                sendLine = '{"ID": ' + str(int(sensorID)) + ', "latitude": "' + latitude + ',' + longitude + '","STATUS": "' + status + '"}' 
                print("launch")
                checker = 1
            elif status == "in range":
                sendLine = '{"ID": ' + str(int(sensorID)) + ',"STATUS": "' + status + '", "DEGREE": ' + degree + '}'
                print("get")
                checker = 1
            elif status == "alert":
                sendLine = '{"ID": ' + str(int(sensorID)) + ',"STATUS": "' + status + '", "DEGREE": ' + degree + '}'
                print("get alert")
                client.publish("alert",sensorID)
                checker = 1
            if checker == 1:
                client.publish("status",sendLine)
                print(sendLine)
                checker = 0

con.close()

Raspberry Pi (MQTTサーバー)

Raspberry Pi上のMQTTサーバはNode-REDで稼働させています。Node-REDをインストールし、パレットにnode-red-contrib-aedes、node-red-contrib-google-sheet、node-red-contrib-momentをインストールしたうえでフローを読み込んでください。
キャプションを入力できます

[
    {
        "id": "52bd3d6007189db0",
        "type": "tab",
        "label": "フロー 1",
        "disabled": false,
        "info": "",
        "env": []
    },
    {
        "id": "a780eb62ce07c8b7",
        "type": "mqtt in",
        "z": "52bd3d6007189db0",
        "name": "",
        "topic": "alert",
        "qos": "2",
        "datatype": "auto-detect",
        "broker": "784977b9995f5ef6",
        "nl": false,
        "rap": true,
        "rh": 0,
        "inputs": 0,
        "x": 130,
        "y": 180,
        "wires": [
            [
                "a23e0dba1bf86ca5"
            ]
        ]
    },
    {
        "id": "65250f1b8e4e3c2c",
        "type": "aedes broker",
        "z": "52bd3d6007189db0",
        "name": "",
        "mqtt_port": 1883,
        "mqtt_ws_bind": "port",
        "mqtt_ws_port": "",
        "mqtt_ws_path": "",
        "cert": "",
        "key": "",
        "certname": "",
        "keyname": "",
        "dburl": "",
        "usetls": false,
        "x": 170,
        "y": 120,
        "wires": [
            [],
            []
        ]
    },
    {
        "id": "a23e0dba1bf86ca5",
        "type": "debug",
        "z": "52bd3d6007189db0",
        "name": "debug 1",
        "active": true,
        "tosidebar": true,
        "console": false,
        "tostatus": false,
        "complete": "false",
        "statusVal": "",
        "statusType": "auto",
        "x": 360,
        "y": 180,
        "wires": []
    },
    {
        "id": "0ad11bb56d099c2d",
        "type": "inject",
        "z": "52bd3d6007189db0",
        "name": "",
        "props": [
            {
                "p": "payload"
            },
            {
                "p": "topic",
                "vt": "str"
            }
        ],
        "repeat": "",
        "crontab": "",
        "once": false,
        "onceDelay": 0.1,
        "topic": "",
        "payload": "",
        "payloadType": "date",
        "x": 160,
        "y": 240,
        "wires": [
            [
                "7c2afbec959c85e6"
            ]
        ]
    },
    {
        "id": "7c2afbec959c85e6",
        "type": "mqtt out",
        "z": "52bd3d6007189db0",
        "name": "",
        "topic": "alert",
        "qos": "",
        "retain": "",
        "respTopic": "",
        "contentType": "",
        "userProps": "",
        "correl": "",
        "expiry": "",
        "broker": "784977b9995f5ef6",
        "x": 370,
        "y": 240,
        "wires": []
    },
    {
        "id": "784977b9995f5ef6",
        "type": "mqtt-broker",
        "name": "",
        "broker": "localhost",
        "port": "1883",
        "clientid": "",
        "autoConnect": true,
        "usetls": false,
        "protocolVersion": "4",
        "keepalive": "60",
        "cleansession": true,
        "birthTopic": "",
        "birthQos": "0",
        "birthPayload": "",
        "birthMsg": {},
        "closeTopic": "",
        "closeQos": "0",
        "closePayload": "",
        "closeMsg": {},
        "willTopic": "",
        "willQos": "0",
        "willPayload": "",
        "willMsg": {},
        "userProps": "",
        "sessionExpiry": ""
    }
]

Google Apps Script

測定したデータは、Googleスプレッドシートへ送信して保存しています。
保存したデータはGoogle Apps Scriptで整形し、各センサ端末の際しの状況はGlideで制作したスマホアプリ上で確認できます。

M5Stack

M5StackはUIFlowで動かしています。MQTTは同じネットワーク内のRaspberry Pi上のサーバを利用しています。接続するWi-FiのSSIDとパスワード、MQTTサーバのIPアドレスを環境に合わせて変更してください。
キャプションを入力できます

組み立て方法

ガーデンライトを分解し改造します。
ガーデンライト全体像

照明部

ガーデンライト上部の太陽電池パネル裏側のネジ二本を外し、筐体を開くとバッテリー充電制御及びLED点灯制御用の基板と電池にアクセスできます。
キャプションを入力できます

理想はソーラーパネルで発電した電力だけで運用することですが、省電力化のノウハウが不十分なため、より容量の大きい充電池に変えて運用します。
付属のバッテリーの容量は200mAhですが、2400mAhのバッテリーに変更しました。
上:使用したバッテリー 下:標準のバッテリー

このガーデンライトはYX8055というICを使い、ソーラーパネルで発電している間はバッテリーを充電し、発電が止まったらLEDを点灯させるようにしています。

制御基板
LEDへの電源供給ケーブルを外し、主電源ラインに移設します。このケーブルを通してSPRESENSEに電源を常時供給します。
ケーブル移設前
ケーブル移設後

次に照明部を改造します。照明部はフードを回転させることで分解可能です。
フードを外すとLEDが取り出せる

取り出したLED基板を反射板から外し、電源ケーブルも取り外します。
キャプションを入力できます

アラート発生時に赤色のフラッシュにするため、樹脂のカバープレートをマジックインキで塗装します。両面から塗ると、きれいに塗装できます。
※LEDを使用しなくても良い場合はここ以降、LED制御基板の制作、接続は不要です。
キャプションを入力できます

アラート用のLEDを点灯させる場合は、LED点灯用の基板を自作します。基板は5穴×6穴サイズにカットしたユニバーサル基板を使用しています。今回は2cm x 8cmサイズの基板をカットして使用しましたが、このサイズには限りません。
2cm x 8cm ユニバーサル基板

LEDを駆動するには、NPNトランジスタを使ってスイッチングします。写真の通り回路を構成して、ガーデンライトのLED基板及び、Spresenseメイン基板と接続します。
LEDスイッチング基板

Spresenseのメイン基板には、Wi-SUNアドオンボードを、さらにその上には加速度センサボードを搭載しています。空いているピンはありませんので、3V3とGNDは加速度センサボードのヘッダピン、D23はWi-SUNアドオンボードのピンソケット根元に直接はんだ付けしています。D23ピンはSPI通信用のピンですが、今回のプロジェクトではSPI通信には使用していないため、LED制御用として使用します。
それぞれのボードの接続用に、片側にQIコネクタメス5PINコネクタを配したケーブルを作って使用してください。
※LEDライトによるアラートを使用しない場合は、ここまでのLED駆動用基板の制作および接続は必要ありません。動作確認は、ArduinoIDEのシリアルモニタのみで行いってください。

LED制御ボードとSpresenseの接続

次の動画は、Spresenseと受信用のRPiにプログラムを書き込み動作確認をしている様子です。起動後にSpresenseを傾け、傾きが閾値を超えるとLEDがフラッシュしています。

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

受信用のRPiでは次のようなメッセージを受け取っています。

Python 3.9.2 (/usr/bin/python3)
>>> %Run wisun_rcv3.py
/dev/ttyUSB0

2022-08-25 17:26:05.813143
ID= LSA0001
STATUS= launch
GNSS= 111.111111, 222.222222

2022-08-25 17:26:23.151292
ID= LSA0001
STATUS= in range
DEGREE= 0.768

2022-08-25 17:26:40.251368
ID= LSA0001
STATUS= alert
DEGREE= 47.673

17:26:05 に起動(status = launch)、GNSSは未実装のためダミーデータです。
17:26:23 deepsleepから復帰して傾きの変化量を測定、0.768°であったため許容範囲内(status=in range)
17:26:40 傾きの変化量が閾値である5°を超えたためアラート発出(status=alert)

Spresense本体の駆動には、ガーデンライトのLED点灯に用いた電源ラインを使用します。供給電圧が低いため、昇圧モジュールHY-106で5Vまで昇圧して使用します。
電源昇圧ボード HY-106

ガーデンライトの制御基板につながる赤及び青のケーブルをHY-106のプラスとマイナス端子にはんだ付けします。
HY-106で5Vまで昇圧された電源は、USB TYPE A コネクタから利用できます。
キャプションを入力できます

SpresenseにHY-106から電源を供給するためのUSBケーブルを接続します。

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

Spresenseと基板類をガーデンライトの照明部の中に入れて、ホットボンドで固定します。

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

分解した際と逆の手順でガーデンライトを組み立てて完成です。

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

まとめ

このプロジェクトでは、hackster で開催された SpresenseコンテストとHackaday Prizeにエントリーしたデバイスをバージョンアップしました。hacksterにエントリーしたVer.1は無線通信機能は持たず、ガーデンライトのLEDの明滅による警報発信のみでした。Hackaday PrizeにエントリーしたVer.2では、Wi-SUN通信機能を追加し、RasPiで作ったサーバとM5stackで作った警報表示装置の機能が追加できました。
今バージョンでは、Google CloudやGlideを使ってスマホからもリアルタイムに情報を確認できるようになりました。これにより、複数端末の管理や場所を選ばず状況確認できるようになり、実用的な装置になったと感じています。しかしながら、省電力化が不十分、及びGNSSによる位置情報取得は未実装のため、さらなる改良が必要です。
通信機能についてもWi-SUN通信だけでは通信エリアに限界があるため、親機を設定してLTEも使ったネットワーク化も視野に入れていますが、システムの複雑化を伴うためもう少し検討が必要です。
コンテストでの腕試しをきっかけにスタートしたプロジェクトですが、社会に役立つオープンソースハードウェアとしての可能性をもう少し追求したいと考えています。

2
airpocketのアイコン画像
電子工作、プログラミング、AI、DIY、XR、IoT M5Stack / Raspberry Pi / Arduino / spresense / K210 / ESP32 / Maix / maicro:bit / oculus / Jetson Nano / minipupper etc
ログインしてコメントを投稿する