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

TWELITE 2021年7月25日

電池持ちがいい無線センサーとして、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

Tags

Great! You've successfully subscribed.
Great! Next, complete checkout for full access.
Welcome back! You've successfully signed in.
Success! Your account is fully activated, you now have access to all content.