TWELite PALの開閉センサーをM5Atom 経由でLINEに通知する
電池持ちがいい無線センサーとして、TWELite PALをよく使っていますが、無線がBluetoothなどではないので、センサの値をインターネット経由で観測するためには、何らかのゲートウェイが必要です。以前は小型のLinuxマシンであるNanoPi NeoにMONOSTICKをつなげて、PythonプログラムでTWELite PALからのシリアルメッセージを受け取るようにしていました。しかし、たまにPythonプログラムが強制終了していたりすると通知が来なくなるので、安定して通知が来るようにM5ATOM を使ったゲートウェイを作ってみました。
ハードウェア
M5ATOMとTWELiteの接続はシリアル通信で良いので、ブレッドボードでも実装できると思いますが、安定動作のために少しきれいにまとめたかったので、ATOM Hub プロトキットを使いました。このキットはM5ATOMを挿せるようになっていて、内部にユニバーサル基盤部分があるので、そこに独自の回路を作れます。今回は横着をして、TWELite DIP を丸ごとユニバーサル基盤の上に内蔵してしまいました。サイズがちょうど良いのです。
TWELite DIPのATOM Hub Protoへの組み込み
アンテナは、TWELite Starter Kitについてきた薄型アンテナを使いました。今回は開閉センサーPALを玄関のドアの開閉監視に使ってみましたが、玄関近くの部屋の中でも十分通信できているようです。
ピンヘッダ付きのTWELite DIPをユニバーサル基盤にそのまま差し込んで、数本足をハンダつけして固定しました。
配線
M5ATOMとTWELite DIPはシリアル通信をするので、電源とシリアルだけつないであげればOKです。TWELite DIPのピン配置表をみて、28番ピンのVCCはATOM Hub Protoの3V3に接続、1番ピンのGNDはATOM Hub ProtoのGNDに接続します。シリアル通信は、ATOM Hub Proto Kitの基盤上でアクセスしやすい、M5ATOMの21番ピンと25番ピンを使うことにしました。M5ATOMの21番ピンをM5ATOMから見たRX, 25番ピンをM5ATOMから見たTXに設定します。M5ATOMの21番ピンとTWELite DIPの10番ピン、M5ATOMの25番ピンとTWELite DIPの3番ピンをそれぞれつなぎます。
そして蓋を閉めれば、ハードウェアとしては完成です。M5ATOMとM5ATOM Liteどちらでも同じソフトウェアが機能します。
ソフトウェア
今回作ったゲートウェイで、TWELite PALのメッセージをインターネット上に送ることができるようになるので、開閉センサーPALの状態に変化があったら、LINE Nofity API でLINEグループなどに通知するようにします。
PlatformIOのArduinoで書かれたソースコード全体は、GitHubに、M5PAL として公開しています。
TWELiteとM5Stackの間の通信を簡単に書けるようにするMWM5 Libraryを使っています。
使用するときは、config.h.example ファイルを利用環境に合わせて変更して、config.h としてビルドしてください。開閉センサーPALの状態はLine Nofityに通知しますが、環境センサーPALのデータをAmbient にも送るようにしているので、WifiのSSID、パスフレーズ、Line Notifyのトークンのほか、AmbientのチャネルIDとライトキーも設定するようになっています。
config.h.example
const char* ssid = "Wifi ESSID";
const char* password = "Wifi Password";
unsigned int channelId = XXXXXXXX; // AmbientのチャネルID
const char* writeKey = "XXXXXXXXXXX"; // ライトキー
const char* token = "XXXXXXXXX"; // Line Notify トークン
M5PAL.cpp send()
本体のM5PAL.cppでは、send関数で指定されたメッセージをLINE Notifyに送っています。 最後にclient.stop();を呼び出していますが、最初はこれを省いていて、通知が数回に1回欠けるという症状に悩まされていました。やはり通信終了したらちゃんと接続は切らないといけませんね。
void send(String message) {
if(WiFi.status() != WL_CONNECTED) wifiConnect();
const char* host = LINEHOST;
client.setInsecure();
if (!client.connect(host, 443)) {
Serial.println("Connection failed");
if(++failcount == 1) M5.Power.reset();
return;
}
Serial.println("Connected");
String query = String("message=") + message;
String request = String("") +
"POST /api/notify HTTP/1.1\r\n" +
"Host: " + host + "\r\n" +
"Authorization: Bearer " + token + "\r\n" +
"Content-Length: " + String(query.length()) + "\r\n" +
"Content-Type: application/x-www-form-urlencoded\r\n\r\n" +
query + "\r\n";
client.print(request);
while (client.connected()) {
String line = client.readStringUntil('\n');
Serial.println(line);
if (line == "\r") {
break;
}
}
String line = client.readStringUntil('\n');
Serial.println(line);
client.stop();
}
M5PAL.cpp setup()
setup関数では、TWELite DIPとの接続に用いるシリアル通信ポート(Serial1) を開き、RX, TXのピン番号を21番と25番に指定しています。
void setup() {
M5.begin(false);
//M5.dis.clear();
Serial1.setRxBufferSize(1024);
Serial.begin(115200);
Serial1.begin(115200,SERIAL_8N1, 21,25);
Serial.println("\nStart");
setenv("TZ","JST-9",1);
wifiConnect();
bool s;
s = ambient.begin(channelId, writeKey, &aclient);
Serial.println(s);
}
M5PAL.cpp loop()
loop関数では、Serial1から読み取ったデータをMWM5のAsciiParserのインスタンスに渡して、MWM5ライブラリで解釈してもらっています。
void loop() {
static char message[300], datetime[30];
int ch;
while (Serial1.available()) {
ch = Serial1.read();
parse_ascii << char_t(ch);
パーサがシリアルポートから送られてきた通信内容の解釈に成功していたら、通信内容からTwePacketクラスのオブジェクトを生成して、パケットの型によって処理を分岐させています。開閉センサーPAL(E_PAL_PCB::MAG) であり、開放を検出したのであれば、玄関ドアが開いたことを知らせるメッセージをLINE Notityに飛ばします。
if (parse_ascii) {
getLocalTime(&timeInfo);
sprintf(datetime, "Time:%04d/%02d/%02d %02d:%02d:%02d",timeInfo.tm_year+1900, timeInfo.tm_mon+1, timeInfo.tm_mday, timeInfo.tm_hour,timeInfo.tm_min,timeInfo.tm_sec);
Serial.println(datetime);
auto&& pkt = newTwePacket(parse_ascii.get_payload());
E_PKT pkt_typ = identify_packet_type(pkt);
if (pkt_typ == E_PKT::PKT_PAL) {
auto&& pal = refTwePacketPal(pkt);
if (pal.u8palpcb == E_PAL_PCB::MAG) {
Serial.println("MAG");
PalMag obj = pal.get_PalMag();
char *status;
switch(obj.u8MagStat) {
case 0:
status = "Open";
sprintf(message,"Door open, %s\n", datetime );
send(message);
break;
default:
status = "Close";
}
sprintf(message, "Magnet PAL notify: %s, BAT %d", status, obj.u16Volt);
Serial.println(message);
同様に、環境センサーPAL(E_PAL_PCB::AMB) であったらAmbientにデータを送り、動作センサーPAL(E_PAL_PCB::MOT)であったら玄関先のミルク受け箱の操作と解釈して、Line Notifyにその旨のメッセージを送っています。
結果
開閉センサーPALが開いたことを検知したり、動作センサーPALが大きな動きを検知したら、LINE Notifyから通知が来るようになりました。