#はじめに
近年の雨の降り方が豪雨で土砂崩れが良く起きています。
土砂崩れは、雨が降っているとき以外でも、雨が降った後、数時間~数日たった後で起こそうです。
そこで、事前にその兆候が判らないか調べるためのIoT杭を考えました。
#IoT杭の条件&センサーの選定
- 多数必要。土砂崩れが起きれば無くなる。(予算とかの問題)
- 電池交換は少な目で。そもそも危険なところに設置するのであまりその場所に行きたくない。
そこで3つのセンサーを検討しました。
一方、杭は、下記製品を検討します。安い!
QUE本体は省電力版が@2,481大電力版が@3,280、杭が@142になります(受信タグを除く)。最初は、受信用タグとセットの製品を購入。
電力の寿命はホームページによると
TWELITEの特徴の一つは電池が長持ちすることです。安価で入手性が良いコイン型電池CR2032で長時間動作します。
以下は電池寿命の例です。送信間隔を長くすると電池寿命を伸ばすことができます。
5秒に1度の定期送信のみの場合、約80日
5秒に1度の定期送信 + 1分に1度TWELITE CUEを動かした場合、約80日
1分に1度の定期送信のみの場合、約700日
1分に1度の定期送信 + 1分に1度TWELITE CUEを動かした場合、約565日
だそうです。 Queの設定変更は、下記を参考にしてください。
#ハードの作成
杭の上にQueを置き、その上から3Dプリンターで作成したカバーをかけて完成。QUEは加速度センサーの方向が書いてある方が上面です。
杭の上面が30x30mm、QUEが30.2×32mmなので少しQueが大きいけどあまり大差ないので問題は無さそう。
ふたのSTLファイルはここに置いています。https://nwws.jpn.org/IoT_Kui/Cover.stl
横のネジは、M4のタップをかけ直した方がよさそうです。M4x5のネジで固定します。
#ソフト
##環境
Windows10Proをサーバー代わりに使用します。
データは時系列データベースのInfluxDBv2を使います。
InfluxDBv2(Windows版)の使い方は、こちらを参考に
##データ受信プログラム(TweLiteCue-->InfluxDB)
※すみません。まだ実運用には無理です。(動作が不安定です。シリアルポートの問題?コンソールの簡易編集の問題?)
C#でコンソールアプリ作成します。IoT杭というプロジェクト名にしました。ほかでも利用できるようにライブラリのファイルは独立させています。
System.IO.Portsがなければ、Nugetで取得します。
InfluxDB.ClientはパッケージマネージャでInstall-Package InfluxDB.Client
でインストール。
また、.NET アプリによっては、シリアルポートの権限を取得するためにPackage.appxmanifestに、下記を追加する必要があったりします。(コンソールアプリは不要?)
<Capabilities>
<Capability Name="internetClient" />
<DeviceCapability Name="serialcommunication" >
<Device Id="any">
<Function Type="name:serialPort"/>
</Device>
</DeviceCapability>
</Capabilities>
###ライブラリ部分
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.IO.Ports;
using System.Diagnostics;
namespace tweliteCue
{
public struct tweliteData
{
public UInt32 中継機シリアルID;
public Byte LQI;
public UInt16 続き番号;
public UInt32 送信元シリアルID;
public Byte 送信元LID;
public Byte センサー種別;
public Byte PAL_IDとPAL_Ver;
public Byte センサーデータ数;
public twe_lite_cue_packet_data センサーデータ0;
public twe_lite_cue_event センサーデータ1;
public UInt32 センサーデータ2_ヘッダ; //拡張ビット有 電圧(電源電圧)
public UInt16 センサーデータ2;
public UInt32 センサーデータ3_ヘッダ;//拡張ビット有 電圧(ADC1)
public UInt16 センサーデータ3;
public UInt32 センサーデータ4_ヘッダ;//1バイト拡張ビットなし ホールIC
public Byte センサーデータ4;
public UInt32 センサーデータ5_ヘッダ;//拡張ビット有 加速度(1サンプル目)
public Int16 センサーデータ5_X軸;
public Int16 センサーデータ5_Y軸;
public Int16 センサーデータ5_Z軸;
public UInt32 センサーデータ6_ヘッダ;//拡張ビット有 加速度(2サンプル目)
public Int16 センサーデータ6_X軸;
public Int16 センサーデータ6_Y軸;
public Int16 センサーデータ6_Z軸;
public UInt32 センサーデータ7_ヘッダ;//拡張ビット有 加速度(3サンプル目)
public Int16 センサーデータ7_X軸;
public Int16 センサーデータ7_Y軸;
public Int16 センサーデータ7_Z軸;
public UInt32 センサーデータ8_ヘッダ;//拡張ビット有 加速度(4サンプル目)
public Int16 センサーデータ8_X軸;
public Int16 センサーデータ8_Y軸;
public Int16 センサーデータ8_Z軸;
public UInt32 センサーデータ9_ヘッダ;//拡張ビット有 加速度(5サンプル目)
public Int16 センサーデータ9_X軸;
public Int16 センサーデータ9_Y軸;
public Int16 センサーデータ9_Z軸;
public UInt32 センサーデータ10_ヘッダ;//拡張ビット有 加速度(6サンプル目)
public Int16 センサーデータ10_X軸;
public Int16 センサーデータ10_Y軸;
public Int16 センサーデータ10_Z軸;
public UInt32 センサーデータ11_ヘッダ;//拡張ビット有 加速度(7サンプル目)
public Int16 センサーデータ11_X軸;
public Int16 センサーデータ11_Y軸;
public Int16 センサーデータ11_Z軸;
public UInt32 センサーデータ12_ヘッダ;//拡張ビット有 加速度(8サンプル目)
public Int16 センサーデータ12_X軸;
public Int16 センサーデータ12_Y軸;
public Int16 センサーデータ12_Z軸;
public UInt32 センサーデータ13_ヘッダ;//拡張ビット有 加速度(9サンプル目)
public Int16 センサーデータ13_X軸;
public Int16 センサーデータ13_Y軸;
public Int16 センサーデータ13_Z軸;
public UInt32 センサーデータ14_ヘッダ;//拡張ビット有 加速度(10サンプル目)
public Int16 センサーデータ14_X軸;
public Int16 センサーデータ14_Y軸;
public Int16 センサーデータ14_Z軸;
public Byte チェックサム1;
public Byte チェックサム2;
public Single 電源電圧mV;
public Single ADC1mV;
public twe_lite_cue_磁石 磁気センサー;
};
public enum twelite_cue_起床要因センサー : Byte
{
磁気センサー = 0x00,
温度 = 0x01,
湿度 = 0x02,
照度 = 0x03,
加速度 = 0x04,
DIO = 0x31,
タイマー = 0x35
}
public enum twelite_cue_起床要因 : Byte
{
イベントが発生した = 0x00,
値が変化した = 0x01,
値が閾値を超えた = 0x02,
閾値を下回った = 0x03,
閾値の範囲に入った = 0x04
}
public struct twe_lite_cue_packet_data
{
public Byte 各種情報ビット値;
public Byte データソース;
public Byte 拡張バイト;
public Byte データ長;
public Byte パケットID;//MSBはイベントがあるかどうか 0もしくは0x80はADC1と電源電圧、イベント以外はデータがないことを示す
public twelite_cue_起床要因センサー 起床要因センサー;//磁気センサー:0x00 温度:0x01 湿度:0x02 照度:0x03 加速度:0x04 DIO:0x31 タイマー:0x35
public twelite_cue_起床要因 起床要因;//イベントが発生した:0x00 値が変化した:0x01 値が閾値を超えた:0x02 閾値を下回った:0x03 閾値の範囲に入った:0x04
}
public enum twe_lite_cue_event_イベントの発生要因 : Byte
{
磁気センサー = 0x00,
温度 = 0x01,
湿度 = 0x02,
照度 = 0x03,
加速度 = 0x04
}
public enum twe_lite_cue_磁石 : Byte
{
磁石がない = 0x00,
N極 = 0x01,
S極 = 0x02,
定期送信 = 0x80
}
public struct twe_lite_cue_event
{
public Byte 各種情報ビット値;
public Byte データソース;
public Byte 拡張バイト;//イベントの発生要因
public twe_lite_cue_event_イベントの発生要因 イベントの発生要因;//磁気センサー:0x00 温度:0x01 湿度:0x02 照度:0x03 加速度:0x04
public Byte データ長;
public Byte データ1;
/**
イベント発生要因が磁気センサーの場合
0x00(0):近くに磁石がない
0x01(1):磁石のN極が近くにある
0x02(2):磁石のS極が近くにある
イベント発生要因が加速度の場合
0x01(1)~0x06(6):さいころ
0x08(8):シェイク
0x10(16):ムーブ
**/
public Int32 データ2;//未使用
}
class twelite : IDisposable
{
SerialPort _port;
string _com;
bool _isInitialize = false;
tweliteData _TweliteData;
bool isIntialize
{
get => _isInitialize;
}
tweliteData TweliteData
{
get => _TweliteData;
}
public twelite(string com)
{
if (!_isInitialize)
{
_isInitialize = true;
_com = com;
_port = new SerialPort(_com, 115200, Parity.None, 8);
_port.DataReceived += new SerialDataReceivedEventHandler(rcv);
_port.Open();
_TweliteData = new tweliteData();
_TweliteData.中継機シリアルID = 10;
}
}
~twelite()
{
if (_port.IsOpen) { _port.Close(); }
}
void IDisposable.Dispose()
{
if (_port.IsOpen) { _port.Close(); }
}
private void rcv(object sender, SerialDataReceivedEventArgs e)
{
// System.Threading.Thread.Sleep(2000);
SerialPort sp = (SerialPort)sender;
string indata = sp.ReadLine();
Debug.WriteLine("Data Received:");
//string indata = sp.ReadExisting();
if (indata.StartsWith(":") == false) return;
Debug.WriteLine(indata);
Debug.WriteLine("indata Length=" + indata.Length);
if (indata.Length != 300) return;
//CRC8
byte crc = 0;
byte crc8 = 0x31;
for (int j = 1; j < 295; j = j + 2)
{
crc ^= Convert.ToByte(indata.Substring(j, 2), 16);
for (int i = 0; i < 8; i++)
{
if ((crc & 0x80) > 0)
{
crc <<= 1; crc ^= crc8;
}
else
{
crc <<= 1;
}
}
}
Debug.WriteLine("CRC CHECK " + indata.Substring(295, 2) + " :" + crc.ToString("x2"));
if (Convert.ToByte(indata.Substring(295, 2), 16) != crc) return;
//LRC
byte lrc = 0;
for (int j = 1; j < 297; j = j + 2)
{
lrc += Convert.ToByte(indata.Substring(j, 2), 16);
}
lrc = (byte)(~lrc);
lrc += 1;
Debug.WriteLine("LRC CHECK " + indata.Substring(297, 2) + " :" + lrc.ToString("x2"));
if (Convert.ToByte(indata.Substring(297, 2), 16) != lrc) return;
_TweliteData.中継機シリアルID = Convert.ToUInt32(indata.Substring(1, 8), 16);
_TweliteData.LQI = Convert.ToByte(indata.Substring(9, 2), 16);
_TweliteData.続き番号 = Convert.ToUInt16(indata.Substring(11, 4), 16);
_TweliteData.送信元シリアルID = Convert.ToUInt32(indata.Substring(15, 8), 16);
_TweliteData.送信元LID = Convert.ToByte(indata.Substring(23, 2), 16);
_TweliteData.センサー種別 = Convert.ToByte(indata.Substring(25, 2), 16);
_TweliteData.PAL_IDとPAL_Ver = Convert.ToByte(indata.Substring(27, 2), 16);
_TweliteData.センサーデータ数 = Convert.ToByte(indata.Substring(29, 2), 16);
//センサーデータ0
if (_TweliteData.センサーデータ数 > 0)
{
_TweliteData.センサーデータ0.各種情報ビット値 = Convert.ToByte(indata.Substring(31, 2), 16);
_TweliteData.センサーデータ0.データソース = Convert.ToByte(indata.Substring(33, 2), 16);
_TweliteData.センサーデータ0.拡張バイト = Convert.ToByte(indata.Substring(35, 2), 16);
_TweliteData.センサーデータ0.データ長 = Convert.ToByte(indata.Substring(37, 2), 16);
_TweliteData.センサーデータ0.パケットID = Convert.ToByte(indata.Substring(39, 2), 16);
_TweliteData.センサーデータ0.起床要因センサー = (twelite_cue_起床要因センサー)Convert.ToByte(indata.Substring(41, 2), 16);
_TweliteData.センサーデータ0.起床要因 = (twelite_cue_起床要因)Convert.ToByte(indata.Substring(43, 2), 16);
}
//センサーデータ1
if (_TweliteData.センサーデータ数 > 1)
{
_TweliteData.センサーデータ1.各種情報ビット値 = Convert.ToByte(indata.Substring(45, 2), 16);
_TweliteData.センサーデータ1.データソース = Convert.ToByte(indata.Substring(47, 2), 16);
_TweliteData.センサーデータ1.拡張バイト = Convert.ToByte(indata.Substring(49, 2), 16);
Byte temp = (byte)((byte)0x7F & _TweliteData.センサーデータ1.拡張バイト);
_TweliteData.センサーデータ1.イベントの発生要因 = (twe_lite_cue_event_イベントの発生要因)temp;
_TweliteData.センサーデータ1.データ長 = Convert.ToByte(indata.Substring(51, 2), 16);
_TweliteData.センサーデータ1.データ1 = Convert.ToByte(indata.Substring(53, 2), 16);
_TweliteData.センサーデータ1.データ2 = Convert.ToInt32(indata.Substring(55, 6), 16);
}
//センサーデータ2 電源電圧
if (_TweliteData.センサーデータ数 > 2)
{
_TweliteData.センサーデータ2_ヘッダ = 0x11300802;
_TweliteData.センサーデータ2 = Convert.ToUInt16(indata.Substring(69, 4), 16);
_TweliteData.電源電圧mV = (Single)_TweliteData.センサーデータ2;
}
//センサーデータ3 ADC1
if (_TweliteData.センサーデータ数 > 3)
{
_TweliteData.センサーデータ3_ヘッダ = 0x11300102;
_TweliteData.センサーデータ3 = Convert.ToUInt16(indata.Substring(81, 4), 16);
_TweliteData.ADC1mV = (Single)_TweliteData.センサーデータ3;
}
//センサーデータ4 ホールIC
if (_TweliteData.センサーデータ数 > 4)
{
_TweliteData.センサーデータ4_ヘッダ = 0x00000001;
_TweliteData.センサーデータ4 = Convert.ToByte(indata.Substring(93, 2), 16);
_TweliteData.磁気センサー = (twe_lite_cue_磁石)(_TweliteData.センサーデータ4 & 0x7f);
}
//センサーデータ5 加速度(1サンプル目)
if (_TweliteData.センサーデータ数 > 5)
{
_TweliteData.センサーデータ5_ヘッダ = 0x15044006;
_TweliteData.センサーデータ5_X軸 = Convert.ToInt16(indata.Substring(103, 4), 16);
_TweliteData.センサーデータ5_Y軸 = Convert.ToInt16(indata.Substring(107, 4), 16);
_TweliteData.センサーデータ5_Z軸 = Convert.ToInt16(indata.Substring(111, 4), 16);
}
//センサーデータ6 加速度(2サンプル目)
if (_TweliteData.センサーデータ数 > 6)
{
_TweliteData.センサーデータ6_ヘッダ = 0x15044006;
_TweliteData.センサーデータ6_X軸 = Convert.ToInt16(indata.Substring(123, 4), 16);
_TweliteData.センサーデータ6_Y軸 = Convert.ToInt16(indata.Substring(127, 4), 16);
_TweliteData.センサーデータ6_Z軸 = Convert.ToInt16(indata.Substring(131, 4), 16);
}
//センサーデータ7 加速度(3サンプル目)
if (_TweliteData.センサーデータ数 > 7)
{
_TweliteData.センサーデータ7_ヘッダ = 0x15044006;
_TweliteData.センサーデータ7_X軸 = Convert.ToInt16(indata.Substring(143, 4), 16);
_TweliteData.センサーデータ7_Y軸 = Convert.ToInt16(indata.Substring(147, 4), 16);
_TweliteData.センサーデータ7_Z軸 = Convert.ToInt16(indata.Substring(151, 4), 16);
}
//センサーデータ8 加速度(4サンプル目)
if (_TweliteData.センサーデータ数 > 8)
{
_TweliteData.センサーデータ8_ヘッダ = 0x15044006;
_TweliteData.センサーデータ8_X軸 = Convert.ToInt16(indata.Substring(163, 4), 16);
_TweliteData.センサーデータ8_Y軸 = Convert.ToInt16(indata.Substring(167, 4), 16);
_TweliteData.センサーデータ8_Z軸 = Convert.ToInt16(indata.Substring(171, 4), 16);
}
//センサーデータ9 加速度(5サンプル目)
if (_TweliteData.センサーデータ数 > 9)
{
_TweliteData.センサーデータ9_ヘッダ = 0x15044006;
_TweliteData.センサーデータ9_X軸 = Convert.ToInt16(indata.Substring(183, 4), 16);
_TweliteData.センサーデータ9_Y軸 = Convert.ToInt16(indata.Substring(187, 4), 16);
_TweliteData.センサーデータ9_Z軸 = Convert.ToInt16(indata.Substring(191, 4), 16);
}
//センサーデータ10 加速度(6サンプル目)
if (_TweliteData.センサーデータ数 > 10)
{
_TweliteData.センサーデータ10_ヘッダ = 0x15044006;
_TweliteData.センサーデータ10_X軸 = Convert.ToInt16(indata.Substring(203, 4), 16);
_TweliteData.センサーデータ10_Y軸 = Convert.ToInt16(indata.Substring(207, 4), 16);
_TweliteData.センサーデータ10_Z軸 = Convert.ToInt16(indata.Substring(211, 4), 16);
}
//センサーデータ11 加速度(7サンプル目)
if (_TweliteData.センサーデータ数 > 11)
{
_TweliteData.センサーデータ11_ヘッダ = 0x15044006;
_TweliteData.センサーデータ11_X軸 = Convert.ToInt16(indata.Substring(223, 4), 16);
_TweliteData.センサーデータ11_Y軸 = Convert.ToInt16(indata.Substring(227, 4), 16);
_TweliteData.センサーデータ11_Z軸 = Convert.ToInt16(indata.Substring(231, 4), 16);
}
//センサーデータ12 加速度(8サンプル目)
if (_TweliteData.センサーデータ数 > 12)
{
_TweliteData.センサーデータ12_ヘッダ = 0x15044006;
_TweliteData.センサーデータ12_X軸 = Convert.ToInt16(indata.Substring(243, 4), 16);
_TweliteData.センサーデータ12_Y軸 = Convert.ToInt16(indata.Substring(247, 4), 16);
_TweliteData.センサーデータ12_Z軸 = Convert.ToInt16(indata.Substring(251, 4), 16);
}
//センサーデータ13 加速度(9サンプル目)
if (_TweliteData.センサーデータ数 > 13)
{
_TweliteData.センサーデータ13_ヘッダ = 0x15044006;
_TweliteData.センサーデータ13_X軸 = Convert.ToInt16(indata.Substring(263, 4), 16);
_TweliteData.センサーデータ13_Y軸 = Convert.ToInt16(indata.Substring(267, 4), 16);
_TweliteData.センサーデータ13_Z軸 = Convert.ToInt16(indata.Substring(271, 4), 16);
}
//センサーデータ14 加速度(10サンプル目)
if (_TweliteData.センサーデータ数 > 14)
{
_TweliteData.センサーデータ14_ヘッダ = 0x15044006;
_TweliteData.センサーデータ14_X軸 = Convert.ToInt16(indata.Substring(283, 4), 16);
_TweliteData.センサーデータ14_Y軸 = Convert.ToInt16(indata.Substring(287, 4), 16);
_TweliteData.センサーデータ14_Z軸 = Convert.ToInt16(indata.Substring(291, 4), 16);
}
DataRcvEventArgs args = new DataRcvEventArgs();
args.RcvData = _TweliteData;
OnDataRcv(args);
}
protected virtual void OnDataRcv(DataRcvEventArgs e)
{
EventHandler<DataRcvEventArgs> handler = DataRcv;
if (handler != null)
{
handler(this, e);
}
}
public event EventHandler<DataRcvEventArgs> DataRcv;
}
public class DataRcvEventArgs : EventArgs
{
public tweliteData RcvData { get; set; }
}
}
###本体
COM5の部分は、PCのデバイスマネージャを見てMonoStickのCOM番号に変更してください。
using System;
using tweliteCue;
using InfluxDB.Client;
using InfluxDB.Client.Api.Domain;
using InfluxDB.Client.Core;
using InfluxDB.Client.Writes;
namespace IoT杭
{
class Program
{
const string token = "書き込みのTokenを書く";
const string bucket = "Bucket名を書く";
const string org = "Org名を書く";
static void Main(string[] args)
{
twelite _twelite;
_twelite = new twelite("COM5");//MonoStickのCOM番号
_twelite.DataRcv += DataRcv;
for (; ; )
{
System.Threading.Thread.Sleep(1000);
}
}
static void DataRcv(object sender, DataRcvEventArgs e)
{
InfluxDBClient client = InfluxDBClientFactory.Create("http://InfluDBのIPアドレス:8086", token.ToCharArray());
float deg_z = e.RcvData.センサーデータ10_Z軸;
float deg_x = e.RcvData.センサーデータ10_X軸;
float deg_y = e.RcvData.センサーデータ10_Y軸;
float deg = (float)(Math.Atan2(deg_z, Math.Sqrt(Math.Pow(deg_x, 2) + Math.Pow(deg_y, 2))) * 180.0 / Math.PI);
string data = "Twelite,ID=" + e.RcvData.送信元LID.ToString() +
" Battery=" + e.RcvData.電源電圧mV.ToString() +
",Degree=" + deg.ToString() +
",LQI=" + e.RcvData.LQI.ToString();
Console.WriteLine(data);
using (var writeApi = client.GetWriteApi())
{
writeApi.WriteRecord(bucket, org, WritePrecision.S, data);
}
}
}
}
##データQueryプログラム(InfluxDB-->C#)
本番運用用のプログラムは未だ未完成なので、サンプルだけ。適当な警報レベルを計算して、DB保管とMQTTで送信するプログラムになる予定。
日時は一旦DateTimeに取り込まないとUTCになったので、このプログラムでは一旦読み込んで表示しています。
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Diagnostics;
using InfluxDB.Client;
using InfluxDB.Client.Api.Domain;
using InfluxDB.Client.Core;
using InfluxDB.Client.Writes;
namespace IoT通知
{
class Program
{
const string token = "読取りのTokenを書く";
const string bucket = "Bucket名";
const string org = "Org名";
struct TweLiteData
{
Int16 degree;
Int16 LQI;
Int32 Battry;
};
static async Task Main(string[] args)
{
Debug.WriteLine("**Start**");
InfluxDBClient client = InfluxDBClientFactory.Create("http://InfluxDBのIP:8086", token.ToCharArray());
string query;
DateTime dd;
//最新データ取得
query = "from(bucket:\"" + bucket + "\") "+
"|> range(start: -1h) " +
"|> filter(fn:(r)=>r._measurement==\"Twelite\")"+
"|>last()";
var tables = await client.GetQueryApi().QueryAsync(query, org);
tables.ForEach(table =>
{
table.Records.ForEach(record =>
{
dd = DateTime.Parse(record.GetTime().ToString());
Debug.WriteLine($"{dd}: {record.GetValueByKey("ID")}:{record.GetValueByKey("_field")}/{record.GetValueByKey("_value")}");
});
});
Debug.WriteLine("*********24時間前***");
//24時間前の最後のデータ取得
query = "from(bucket:\"" + bucket + "\") " +
"|> range(start: -48h ,stop:-24h) " +
"|> filter(fn:(r)=>r._measurement==\"Twelite\")" +
"|>last()";
tables = await client.GetQueryApi().QueryAsync(query, org);
tables.ForEach(table =>
{
table.Records.ForEach(record =>
{
dd = DateTime.Parse(record.GetTime().ToString());
Debug.WriteLine($"{dd}: {record.GetValueByKey("ID")}:{record.GetValueByKey("_field")}/{record.GetValueByKey("_value")}");
});
});
client.Dispose();
}
}
}
#あとは
実際に電波がどれくらい届くかのチェックとか中継の検討とかをしないといけません。
TweLite Queの受信の部分を別クラスにしているので、Hololensでも使えないか試してみたいと思います。