編集履歴一覧に戻る
airpocketのアイコン画像

airpocket が 2022年09月22日00時42分02秒 に編集

コメント無し

本文の変更

# はじめに 日本は山岳地帯が国土の大半を占め、なおかつ降雨量が非常に多いという特徴を持っています。この地理的な要因により、毎年の台風シーズンには全国各地で大規模な土砂災害による被害が出ています。 多くの被害を出す土砂災害の一つに地すべりがあります。地すべりにはいくつかの予兆があると指摘されており、その中に、「段差・はらみ出し」「樹木の傾きや変化」について言及されています。地すべりの発生前には、わずかな地形の変化が発生しこの変化が種々の予兆現象として観測されています。この予兆現象を検出して事前の避難につなげられれば、人的被害を減少させることができそうです。 参考:農林水産省資料「[地すべりの前兆現象の種類と観測方法](https://www.maff.go.jp/j/nousin/noukan/tyotei/t_zisuberi/pdf/yobou_tebiki_2.pdf)」 すでに多様な災害対策が行われているものの、広大な危険地域すべてを完全にカバーすることは困難なため、十分な対策を打てているとは言えません。今後もすべてのリスクをカバーすることはできないでしょう。 このプロジェクトでは、土砂災害の中でも多くの被害を出している地すべりの予兆を検知してアラートを発する装置を開発しました。 # 装置の特長 この装置はSpresenseを使用し、可能な限り入手しやすい部材を利用してDIY可能な警報システムを構築構築することを目標としています。山岳部や山すその傾斜地などに居住する方が、自宅の裏山や敷地内に簡易的に設置するという利用方法を想定しています。 既存の技術について調査したところ、[同様のコンセプトの商品](https://xtech.nikkei.com/atcl/nxt/column/18/00142/00577/)がすでに存在していることも判りました。しかしながら、どこで発生するか判らない予兆現象の検出を行うためには多数のデバイスを運用する必要があるため、端末価格のさらなる低減は重要な課題と言えます。 このプロジェクトでは、多機能であることよりもシンプルな操作性を、電子、電気の知識が乏しい方でもなるべく簡単に実装できる装置の開発を主眼としました。 筐体にはDaisoで購入できるガーデンライトを利用し、ショップで購入する事のできる部材の組み合わせで主要な機能を実装できます。 ## 主な機能と特徴 ・ガーデンライトを利用した筐体により、突き刺すだけの簡単設置 ・加速度センサにより、装置の傾きの変化を検出 ・ガーデンライトのLEDを利用してフラッシュによる警報表示 ・Wi-SUN通信によるアラート信号の送信 ・受信側デバイスからMQTTにて任意の端末へアラート情報を送信 ・警報表示デバイスがアラート情報を受け取り警報表示 ・ガーデンライトの太陽電池パネルによる電源の補完 ・複数デバイスのデータをGoogleスプレッドシートに保存(データ処理の一部は開発中) ・Googleスプレッドシートのデータをスマホアプリ上に表示(GNSSデータをもとに設置場所を地図上に表示) ## 追加予定の機能 ・電源の強化と省電力化  現在の電源では、一時間ごとの間欠駆動で約一週間しか稼働できないため、省電力化及び電源容量の拡張が必要。 ・GoogleスプレッドシートのGASによる情報更新は開発中。もうすぐできます。時間が足りないー💦 ・GNSS外部アンテナ化  GNSSによる設置位置取得機能についてはオンボードアンテナではデータ取得に難あり、外部アンテナ引き出しが必要。現在は位置情報は手動入力。 # 稼働している様子 フィールドテストの様子です。 @[youtube](https://www.youtube.com/watch?v=OrEw94bvgL0) @[youtube](https://www.youtube.com/watch?v=SXwZy36jEXo) デバイスのステータス表示アプリの様子です。表示にはダミーデータを使用しています。 @[youtube](https://www.youtube.com/watch?v=l6nQQ13pVJY) データを保存するスプレッドシート。 ![キャプションを入力できます](https://camo.elchika.com/0d2e11a0b014c453f301415d2b4e642d008fdbe8/687474703a2f2f73746f726167652e676f6f676c65617069732e636f6d2f656c6368696b612f76312f757365722f38666662353561372d373433312d346234382d383832642d3565323665663534343465652f34313435636435302d633665612d343238302d613461622d356663616363386634363264/) ## 設置時 装置を設置する際には、監視したい場所に装置を挿して自立させ、電源を入れるだけです。(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情報を元に設置位置を地図上に表示します。 # システム構成 全体のシステム構成は次の通りです。 ![システム構成図](https://camo.elchika.com/230cf6d50ebebca06f078d0706166d58ad736419/687474703a2f2f73746f726167652e676f6f676c65617069732e636f6d2f656c6368696b612f76312f757365722f38666662353561372d373433312d346234382d383832642d3565323665663534343465652f35343037666338322d353562622d346534642d383263362d626563366436653464346537/) センサー部のコントローラーにはSpresenseを使用し、BMI160の加速度センサの値からデバイスの傾きを計算します。測定結果は、Wi-SUN通信で受信装置へ送信しています。受信装置のRaspberry PiはpythonでWi-SUN通信やデータの処理を行い、Node-REDでMQTTサーバを稼働、ディスプレイ装置に警報情報を送信します。ディスプレイ装置にはM5Stackを使用し、音と画面表示で警報を表示します。 # 部品リスト | パーツ名 | 数量 | |:---:|:---:| |[ソーラーガーデンライト(スポットライト)](https://jp.daisonet.com/products/4549131730036?_pos=3&_sid=56611814e&_ss=r)|1| | [Spresense Main Board](https://developer.sony.com/ja/develop/spresense/) | 1 | |[Wi-SUN Add-onボード SPRESENSE-WiSUN-EVK-701](https://www.rohm.co.jp/support/spresense-add-on-board) |1| |[SPRESENSE用3軸加速度・3軸ジャイロ・気圧・温度センサ アドオンボード](https://www.switch-science.com/catalog/5258/)|1| |[DC-DC昇圧ボード HW-106](https://www.amazon.co.jp/KKHMF-10-DC-DC-0-9V-5V-%E9%9B%BB%E5%9C%A7%E5%A4%89%E6%8F%9B%E3%82%B9%E3%83%86%E3%83%83%E3%83%97%E3%82%A2%E3%83%83%E3%83%97%E3%83%96%E3%83%BC%E3%82%B9%E3%82%BF%E3%83%BC%E3%83%A2%E3%82%B8%E3%83%A5%E3%83%BC%E3%83%AB/dp/B0785KBRKV)|1| |[2cm x 8cm ユニバーサル基板](https://www.amazon.co.jp/PCB%E5%9B%9E%E8%B7%AF%E5%9F%BA%E6%9D%BF-10%E6%9E%9A%E3%82%BB%E3%83%83%E3%83%882%C3%978cm-%E3%83%A6%E3%83%8B%E3%83%90%E3%83%BC%E3%82%B5%E3%83%AB%E5%9F%BA%E6%9D%BF-PCB%E3%83%97%E3%83%AD%E3%83%88%E3%82%BF%E3%82%A4%E3%83%97%E3%83%9C%E3%83%BC%E3%83%89-%E5%AE%9F%E9%A8%93%E3%83%97%E3%83%AC%E3%83%BC%E3%83%88/dp/B09JVJ3XT5/ref=sr_1_36?__mk_ja_JP=%E3%82%AB%E3%82%BF%E3%82%AB%E3%83%8A&crid=2MPODAOMNUJOB&keywords=%E3%83%A6%E3%83%8B%E3%83%90%E3%83%BC%E3%82%B5%E3%83%AB%E5%9F%BA%E6%9D%BF&qid=1661399629&s=industrial&sprefix=%E3%83%A6%E3%83%8B%E3%83%90%E3%83%BC%E3%82%B5%E3%83%AB%E5%9F%BA%E6%9D%BF%2Cindustrial%2C193&sr=1-36)|1| |[NPNトランジスタ BC550](https://www.amazon.co.jp/20PCS-BC550B-BC550-%E3%83%88%E3%83%A9%E3%83%B3%E3%82%B8%E3%82%B9%E3%82%BF-100MA/dp/B07R6F1LRY/ref=sr_1_2?keywords=BC550&qid=1661399703&sr=8-2)|1| |[抵抗 10Ω](https://www.amazon.co.jp/4%EF%BC%B71-10%CE%A9-20%E6%9C%AC%E5%85%A5-%E6%8A%B5%E6%8A%97-%E3%82%AB%E3%83%BC%E3%83%9C%E3%83%B3%E6%8A%B5%E6%8A%97%EF%BC%9Cres-703%EF%BC%9E/dp/B00T9590M6/ref=sr_1_3?__mk_ja_JP=%E3%82%AB%E3%82%BF%E3%82%AB%E3%83%8A&crid=9K0PW1VDV6LX&keywords=%E6%8A%B5%E6%8A%97+10%CE%A9&qid=1661399808&s=industrial&sprefix=%E6%8A%B5%E6%8A%97+10%CF%89%2Cindustrial%2C203&sr=1-3)|1| |[NiMH 単三バッテリー](https://www.amazon.co.jp/Amazon%E3%83%99%E3%83%BC%E3%82%B7%E3%83%83%E3%82%AF-AmazonBasics-HR-3UTG-AMZN-%E5%85%85%E9%9B%BB%E5%BC%8F%E3%83%8B%E3%83%83%E3%82%B1%E3%83%AB%E6%B0%B4%E7%B4%A0%E9%9B%BB%E6%B1%A0-%E6%9C%80%E5%B0%8F%E5%AE%B9%E9%87%8F1900mAh%E3%80%81%E7%B4%841000%E5%9B%9E%E4%BD%BF%E7%94%A8%E5%8F%AF%E8%83%BD/dp/B00CWNMV4G/ref=sr_1_6?keywords=nimh+%E5%8D%983&qid=1661399862&sr=8-6)|1| |[ピンヘッダ](https://www.amazon.co.jp/uxcell-a14032500ux0426-%E3%83%94%E3%83%B3%E3%83%98%E3%83%83%E3%83%80-PCB%E7%94%A8%E3%83%94%E3%83%B3%E3%83%98%E3%83%83%E3%83%80-40P-%E7%9B%B4%E8%A7%92-%E3%82%AA%E3%82%B9-%E3%82%B4%E3%83%BC%E3%83%AB%E3%83%89%E3%83%88%E3%83%BC%E3%83%B3-%E9%96%93%E9%9A%94-10%E5%80%8B%E5%85%A5%E3%82%8A-2-54mm-%E3%83%96%E3%83%A9%E3%83%83%E3%82%AF/dp/B010ESD338/ref=sr_1_3_sspa?__mk_ja_JP=%E3%82%AB%E3%82%BF%E3%82%AB%E3%83%8A&crid=2S7DIN9L1E8A3&keywords=%E3%83%94%E3%83%B3%E3%83%98%E3%83%83%E3%83%80&qid=1661399971&sprefix=%E3%83%94%E3%83%B3%E3%83%98%E3%83%83%E3%83%80%2Caps%2C201&sr=8-3-spons&psc=1&smid=AANM8PRMV1MBN&spLa=ZW5jcnlwdGVkUXVhbGlmaWVyPUFEUzVVNUFMMzcxM0ImZW5jcnlwdGVkSWQ9QTA3MjQ1MzEyUjY1TlZWU0NaMlEzJmVuY3J5cHRlZEFkSWQ9QTE4QUo3WExUVkxBM0Imd2lkZ2V0TmFtZT1zcF9hdGYmYWN0aW9uPWNsaWNrUmVkaXJlY3QmZG9Ob3RMb2dDbGljaz10cnVl)(L字5ピン)|1| |[QIコネクタ](https://www.amazon.co.jp/knoweasy-620pcs-2-54mm%E3%83%94%E3%83%83%E3%83%81-QI%E3%82%B3%E3%83%8D%E3%82%AF%E3%82%BF-%E3%83%87%E3%83%A5%E3%83%9D%E3%83%B3%E3%82%B3%E3%83%8D%E3%82%AF%E3%82%BF/dp/B0863P2YV8/ref=sr_1_1_sspa?__mk_ja_JP=%E3%82%AB%E3%82%BF%E3%82%AB%E3%83%8A&crid=2LHF5D1J65LJR&keywords=%E3%83%87%E3%83%A5%E3%83%9D%E3%83%B3%E3%82%B3%E3%83%8D%E3%82%AF%E3%82%BF&qid=1661400047&sprefix=%E3%83%87%E3%83%A5%E3%83%9D%E3%83%B3%E3%82%B3%E3%83%8D%E3%82%AF%E3%82%BF%2Caps%2C243&sr=8-1-spons&psc=1&spLa=ZW5jcnlwdGVkUXVhbGlmaWVyPUFaNksyQ1FWS1JaMTgmZW5jcnlwdGVkSWQ9QTAzNzAwMDIyWDJCM0ZYTTI5QjJXJmVuY3J5cHRlZEFkSWQ9QTM3NDJSOFlOQUdBRjImd2lkZ2V0TmFtZT1zcF9hdGYmYWN0aW9uPWNsaWNrUmVkaXJlY3QmZG9Ob3RMb2dDbGljaz10cnVl)(メス5ピン)|1| |[ケーブル](https://www.amazon.co.jp/TUOFENG-%E3%83%A9%E3%82%A4%E3%83%B324%E3%80%81%E3%83%AF%E3%82%A4%E3%83%A4%E3%83%BC%E3%82%AD%E3%83%83%E3%83%8824-AWG%E3%83%95%E3%83%AC%E3%82%AD%E3%82%B7%E3%83%96%E3%83%AB%E3%82%B7%E3%83%AA%E3%82%B3%E3%83%B3%E3%83%AF%E3%82%A4%E3%83%A4%E3%83%BC%EF%BC%88%E8%89%B2%E3%81%82%E3%81%9F%E3%82%8A9-m%E3%82%B9%E3%83%97%E3%83%BC%E3%83%AB%E3%81%AE6%E8%89%B2%EF%BC%89300-V%E3%82%B1%E3%83%BC%E3%83%96%E3%83%AB%E8%80%90%E7%86%B1%E6%80%A7/dp/B07GGZQXSF/ref=sr_1_1?__mk_ja_JP=%E3%82%AB%E3%82%BF%E3%82%AB%E3%83%8A&crid=10FCBV8A1NXN5&keywords=awg24+%E3%82%B7%E3%83%AA%E3%82%B3%E3%83%B3&qid=1661400155&sprefix=awg24+%E3%82%B7%E3%83%AA%E3%82%B3%E3%83%BC%E3%83%B3%2Caps%2C193&sr=8-1)|適量| |USB micro 10cm|1| |[Raspberry Pi 4B](https://www.raspberrypi.com/products/raspberry-pi-4-model-b/)|1| |[Wi-SUN USBドングル BP35C2](https://www.rohm.co.jp/products/wireless-communication/specified-low-power-radio-modules/bp35c2-product)|1| |[M5Stack Basic](https://www.switch-science.com/catalog/7362/)|1| # 回路図(LED点灯用基板) この基板はLED点灯機能を実装する場合に必要ですが、アラートを無線出力のみで行う場合は必要ありません。 ![キャプションを入力できます](https://camo.elchika.com/3f4dcec5980e9e2864e79a36f1159a5b42c35111/687474703a2f2f73746f726167652e676f6f676c65617069732e636f6d2f656c6368696b612f76312f757365722f38666662353561372d373433312d346234382d383832642d3565323665663534343465652f38326431333538382d663937612d343166312d616334322d386432613033373738643334/) # プログラム このシステムには、Spresenseを使用したセンサ装置のほかにRaspberry Piを使用した受信装置とM5Stackを使用したディスプレイ装置が含まれます。それぞれで使用するプログラムは次の通りです。 ## Spresense ### メインスケッチ ```arduino #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さんのライブラリはこちら](https://github.com/RohmSemiconductor/Arduino/tree/master/SPRESENSE-WISUN-EVK-701)。 [変更点の詳細説明記事はこちら](https://elchika.com/article/bb1bf833-6bd7-4c95-923f-93cf5239ef05/)。 bp35c0-j11.h ```arduino /* 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 ```arduino /******************************************************************************** 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通信部分のプログラムは[デバイスプラスのこちらの記事](https://deviceplus.jp/mc-general/weather-station-with-wisun-03/)を参考にして一部を変更の上利用させていただきました。 MQTT通信部分のプログラムは[こちらの記事](https://qiita.com/hsgucci/items/6461d8555ea1245ef6c2)を参考にして利用させていただきました。 ### Wi-SUN Dongle初期設定 Wi-SUN通信用のUSBドングルを利用する際には、このプログラムで初期化する必要があります。 この初期化は、Raspberry Piを起動する都度必要です。Raspberry Pi稼働中にUSBドングルを抜き差しすると、ポートが変化する為注意してください。 ```python # /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送信 ```python #!/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をインストールしたうえでフローを読み込んでください。 ![キャプションを入力できます](https://camo.elchika.com/7b3160053abff293a3d315a3236558f985ce7604/687474703a2f2f73746f726167652e676f6f676c65617069732e636f6d2f656c6368696b612f76312f757365722f38666662353561372d373433312d346234382d383832642d3565323665663534343465652f66393532663832332d373638312d346432342d613137352d363632323066396335363134/) ```json [ { "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アドレスを環境に合わせて変更してください。 ![キャプションを入力できます](https://camo.elchika.com/560784d6b16cc0645cf0997b2705ccab36d9447d/687474703a2f2f73746f726167652e676f6f676c65617069732e636f6d2f656c6368696b612f76312f757365722f38666662353561372d373433312d346234382d383832642d3565323665663534343465652f30303765333863302d363265322d343165362d623639322d303431633165616436643935/) # 組み立て方法 ガーデンライトを分解し改造します。 ![ガーデンライト全体像](https://camo.elchika.com/5936fdcde23d64a213c6cc80b99772665694b3b1/687474703a2f2f73746f726167652e676f6f676c65617069732e636f6d2f656c6368696b612f76312f757365722f38666662353561372d373433312d346234382d383832642d3565323665663534343465652f63646438386334352d623331332d346536662d383765332d306636656636313263396431/) ![照明部](https://camo.elchika.com/93d0aeb72bd1e60b29a0b49742a7f409c00afcbf/687474703a2f2f73746f726167652e676f6f676c65617069732e636f6d2f656c6368696b612f76312f757365722f38666662353561372d373433312d346234382d383832642d3565323665663534343465652f33373465306531662d313866632d346537332d383930352d363036643263383433326533/) ガーデンライト上部の太陽電池パネル裏側のネジ二本を外し、筐体を開くとバッテリー充電制御及びLED点灯制御用の基板と電池にアクセスできます。 ![キャプションを入力できます](https://camo.elchika.com/49e9703a60e8a9407ed0fa26683268a52e8cb360/687474703a2f2f73746f726167652e676f6f676c65617069732e636f6d2f656c6368696b612f76312f757365722f38666662353561372d373433312d346234382d383832642d3565323665663534343465652f35333831663939642d656439322d343762662d393731372d633366623237336138666633/) 理想はソーラーパネルで発電した電力だけで運用することですが、省電力化のノウハウが不十分なため、より容量の大きい充電池に変えて運用します。 付属のバッテリーの容量は200mAhですが、2400mAhのバッテリーに変更しました。 ![上:使用したバッテリー 下:標準のバッテリー](https://camo.elchika.com/3221e4675dbcf92b921ef8da150b0ce26e39f653/687474703a2f2f73746f726167652e676f6f676c65617069732e636f6d2f656c6368696b612f76312f757365722f38666662353561372d373433312d346234382d383832642d3565323665663534343465652f32643638333739362d643364632d346666362d393939382d616666343264303764653039/) このガーデンライトはYX8055というICを使い、ソーラーパネルで発電している間はバッテリーを充電し、発電が止まったらLEDを点灯させるようにしています。 ![制御基板](https://camo.elchika.com/0108c139cd8c68d87983b7d5cd19f70f12a0a9e9/687474703a2f2f73746f726167652e676f6f676c65617069732e636f6d2f656c6368696b612f76312f757365722f38666662353561372d373433312d346234382d383832642d3565323665663534343465652f36323833333835372d323433642d343236642d383635392d636335356632396363656562/) LEDへの電源供給ケーブルを外し、主電源ラインに移設します。このケーブルを通してSPRESENSEに電源を常時供給します。 ![ケーブル移設前](https://camo.elchika.com/a763627190cac253d6a9166e3bd0ec93547e865c/687474703a2f2f73746f726167652e676f6f676c65617069732e636f6d2f656c6368696b612f76312f757365722f38666662353561372d373433312d346234382d383832642d3565323665663534343465652f31656230366634352d323365632d343430362d613339362d633335643937326530633366/) ![ケーブル移設後](https://camo.elchika.com/234539ec831bb2f918c3b817b8158a23b5e966d2/687474703a2f2f73746f726167652e676f6f676c65617069732e636f6d2f656c6368696b612f76312f757365722f38666662353561372d373433312d346234382d383832642d3565323665663534343465652f64383031386235372d396532392d343139652d626335302d303738613533363963666561/) 次に照明部を改造します。照明部はフードを回転させることで分解可能です。 ![フードを外すとLEDが取り出せる](https://camo.elchika.com/173c88f42ff401c436a28375ea4508ef067fc2ae/687474703a2f2f73746f726167652e676f6f676c65617069732e636f6d2f656c6368696b612f76312f757365722f38666662353561372d373433312d346234382d383832642d3565323665663534343465652f37373132333866362d383638632d343138632d626666332d363965663365636432633763/) 取り出したLED基板を反射板から外し、電源ケーブルも取り外します。 ![キャプションを入力できます](https://camo.elchika.com/ef4cf1a6ad7ed0b682c8a3d9bb8698db16e5ae06/687474703a2f2f73746f726167652e676f6f676c65617069732e636f6d2f656c6368696b612f76312f757365722f38666662353561372d373433312d346234382d383832642d3565323665663534343465652f38386164623034392d396238372d346264342d396437342d653361656432376462623665/) アラート発生時に赤色のフラッシュにするため、樹脂のカバープレートをマジックインキで塗装します。両面から塗ると、きれいに塗装できます。 ※LEDを使用しなくても良い場合はここ以降、LED制御基板の制作、接続は不要です。 ![キャプションを入力できます](https://camo.elchika.com/e136f243f5d6136517c07361a911c7efd0ee1ea0/687474703a2f2f73746f726167652e676f6f676c65617069732e636f6d2f656c6368696b612f76312f757365722f38666662353561372d373433312d346234382d383832642d3565323665663534343465652f63313736333030312d323364392d346531332d613930302d306539366366366164343464/) アラート用のLEDを点灯させる場合は、LED点灯用の基板を自作します。基板は5穴×6穴サイズにカットしたユニバーサル基板を使用しています。今回は2cm x 8cmサイズの基板をカットして使用しましたが、このサイズには限りません。 ![2cm x 8cm ユニバーサル基板](https://camo.elchika.com/d1a040bcf35b1fa0cb3501629231bfb5c3e8cc6b/687474703a2f2f73746f726167652e676f6f676c65617069732e636f6d2f656c6368696b612f76312f757365722f38666662353561372d373433312d346234382d383832642d3565323665663534343465652f62643733663331312d633937372d343362622d613161332d633331383663333462313835/) LEDを駆動するには、NPNトランジスタを使ってスイッチングします。写真の通り回路を構成して、ガーデンライトのLED基板及び、Spresenseメイン基板と接続します。 ![LEDスイッチング基板](https://camo.elchika.com/07d5b18c97b45d5efcc898b052ca1329e8bce6eb/687474703a2f2f73746f726167652e676f6f676c65617069732e636f6d2f656c6368696b612f76312f757365722f38666662353561372d373433312d346234382d383832642d3565323665663534343465652f65626235373737612d653666652d343231322d616463302d616430376561386262356535/) Spresenseのメイン基板には、Wi-SUNアドオンボードを、さらにその上には加速度センサボードを搭載しています。空いているピンはありませんので、3V3とGNDは加速度センサボードのヘッダピン、D23はWi-SUNアドオンボードのピンソケット根元に直接はんだ付けしています。D23ピンはSPI通信用のピンですが、今回のプロジェクトではSPI通信には使用していないため、LED制御用として使用します。 それぞれのボードの接続用に、片側にQIコネクタメス5PINコネクタを配したケーブルを作って使用してください。 ※LEDライトによるアラートを使用しない場合は、ここまでのLED駆動用基板の制作および接続は必要ありません。動作確認は、ArduinoIDEのシリアルモニタのみで行いってください。 ![LED制御ボードとSpresenseの接続](https://camo.elchika.com/9e79e78c47ffab48e0f64182f9857fc9ee1c8dcb/687474703a2f2f73746f726167652e676f6f676c65617069732e636f6d2f656c6368696b612f76312f757365722f38666662353561372d373433312d346234382d383832642d3565323665663534343465652f37386539333934322d396632642d343264332d393663652d393836346336333464383561/) 次の動画は、Spresenseと受信用のRPiにプログラムを書き込み動作確認をしている様子です。起動後にSpresenseを傾け、傾きが閾値を超えるとLEDがフラッシュしています。 @[youtube](https://www.youtube.com/watch?v=UoYLHeIHIOM) 受信用のRPiでは次のようなメッセージを受け取っています。 ```shell 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= 1111/2222 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](https://camo.elchika.com/11fe3b0ecb829b262de41a7731b82288ed1c8236/687474703a2f2f73746f726167652e676f6f676c65617069732e636f6d2f656c6368696b612f76312f757365722f38666662353561372d373433312d346234382d383832642d3565323665663534343465652f65626134326532372d323165612d343533642d386663312d363534646334653333656638/) ガーデンライトの制御基板につながる赤及び青のケーブルをHY-106のプラスとマイナス端子にはんだ付けします。 HY-106で5Vまで昇圧された電源は、USB TYPE A コネクタから利用できます。 ![キャプションを入力できます](https://camo.elchika.com/2fa4c25e61a82eae1dea4f8b1cb3d9c2104a938b/687474703a2f2f73746f726167652e676f6f676c65617069732e636f6d2f656c6368696b612f76312f757365722f38666662353561372d373433312d346234382d383832642d3565323665663534343465652f34663531376131642d653739622d346363652d396562372d373432616365366234666433/) SpresenseにHY-106から電源を供給するためのUSBケーブルを接続します。 ![キャプションを入力できます](https://camo.elchika.com/e2c4487259e2abf53256a23f472af066d082437a/687474703a2f2f73746f726167652e676f6f676c65617069732e636f6d2f656c6368696b612f76312f757365722f38666662353561372d373433312d346234382d383832642d3565323665663534343465652f62303962653564312d636339392d346134312d616261342d353265633064366161643565/) Spresenseと基板類をガーデンライトの照明部の中に入れて、ホットボンドで固定します。 ![キャプションを入力できます](https://camo.elchika.com/e600b6083987383458e0de2972d5a10a48e80b1c/687474703a2f2f73746f726167652e676f6f676c65617069732e636f6d2f656c6368696b612f76312f757365722f38666662353561372d373433312d346234382d383832642d3565323665663534343465652f39306536323939372d333866342d343135332d623930612d666561616362353134373838/) 分解した際と逆の手順でガーデンライトを組み立てて完成です。 ![キャプションを入力できます](https://camo.elchika.com/5d7bd4ffc72527ac4a7927dcc8026459c0e44dcf/687474703a2f2f73746f726167652e676f6f676c65617069732e636f6d2f656c6368696b612f76312f757365722f38666662353561372d373433312d346234382d383832642d3565323665663534343465652f37393939373730332d636236392d343739362d623033352d303266373062363261663163/) # まとめ このプロジェクトでは、Hackster で開催された Spresenseコンテストにエントリーしたデバイスをバージョンアップしました。前のバージョンは無線通信機能は持たず、ガーデンライトのLEDの明滅による警報発信のみでした。 今バージョンでは、Wi-SUN通信によるアラートの無線発信、ラズベリーパイとM5Stackを利用した警報表示機能を追加しました。これにより、ガーデンライトのLEDの明滅による警報表示よりも実用的な装置になったと感じています。しかしながら、省電力化及びバッテリー容量はまだ不足しており、さらなる改良が必要です。 通信機能についてもWi-SUN通信だけでは通信エリアに限界があるため、LoRaなどを利用した母機とのネットワーク化も有効かと感じていますが、システムの複雑化を伴うためもう少し検討が必要です。 コンテストでの腕試しをきっかけにスタートしたプロジェクトですが、社会に役立つオープンソースハードウェアとしての可能性をもう少し追求したいと考えています。