TWELite PALの開閉センサーをM5Atom 経由でLINEに通知する

TWELite PALの開閉センサーをM5Atom 経由でLINEに通知する

電池持ちがいい無線センサーとして、TWELite PALをよく使っていますが、無線がBluetoothなどではないので、センサの値をインターネット経由で観測するためには、何らかのゲートウェイが必要です。以前は小型のLinuxマシンであるNanoPi NeoMONOSTICKをつなげて、PythonプログラムでTWELite PALからのシリアルメッセージを受け取るようにしていました。しかし、たまにPythonプログラムが強制終了していたりすると通知が来なくなるので、安定して通知が来るようにM5ATOM を使ったゲートウェイを作ってみました。

ハードウェア

M5ATOMとTWELiteの接続はシリアル通信で良いので、ブレッドボードでも実装できると思いますが、安定動作のために少しきれいにまとめたかったので、ATOM Hub プロトキットを使いました。このキットはM5ATOMを挿せるようになっていて、内部にユニバーサル基盤部分があるので、そこに独自の回路を作れます。今回は横着をして、TWELite DIP を丸ごとユニバーサル基盤の上に内蔵してしまいました。サイズがちょうど良いのです。

TWELite DIPのATOM Hub Protoへの組み込み

ATOM Hub ProtoとTWELite DIP

アンテナは、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番ピンをそれぞれつなぎます。

ATOM Hub Protoのユニバーサル基盤にTWELite DIPを載せる
シリアル, 電源, GNDを接続

そして蓋を閉めれば、ハードウェアとしては完成です。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から通知が来るようになりました。

LINE Notify