戸締り確認&通知センサーをマイコンで自作 part3 通知LED点灯

戸締り確認&通知センサーをマイコンで自作 part3 通知LED点灯

戸締り確認&通知センサーをマイコンで自作 part3では、扉 窓の開閉状態を検知する為、TWELITEとArduino間をシリアル通信させ、開閉通知LEDを点灯させます。
システムの概要は第1回で解説していますので、下記リンクをご覧ください。

TWILITEとArduinoをシリアル通信させる

前回のpart2でTWILITE DIPとTWILITE PALを通信させ、その結果が文字列として出力されることは確認できました。
この文字列をArduinoに送信する為、 TWILITEとArduinoをシリアル通信させる必要があります。
幸いなことにどちらも標準機能としてシリアル通信(UART)が実装されていますので、難しいことはありません。
相互通信をさせないのであれば
TWILITE DIP (TX)

Arduino Uno (RX)
を配線するだけでOKです。

受信したデータをArduinoで解読

配線図

Arduinoを用いた通知システムの配線図です。
Arduinoを駆動する電源はUSBからで問題ありません。LEDの数を増やしていくと電源供給が間に合わなくなるので、Vinからの供給に変更したほうが良いでしょう。
LEDは抵抗内蔵の5V駆動品を使ってます。通常のLEDの場合は前段に抵抗を挟んで供給を調整してください。

TWILITE DIPには駆動用の3.3VとGND、そしてシリアル通信用にTXを接続します。

Arduinoコード

#include <SoftwareSerial.h>
#include <stdio.h>

// typedef
typedef unsigned short uint16;
typedef unsigned long uint32;

// 受信したデータを保管する構造体
typedef struct {
  bool ExPacket;      // 拡張形式か否か
  byte LID;           // 送信元の論理デバイスID
  byte RspID;         // 応答ID
  uint16 Length;      // データの長さ
  byte Data[255];     // データ
  byte LQI;           // 通信品質(高いほど良い)
  byte Status;        // 開閉状態(0=Close,1or2=Open,82=定期通信)
  int  Voltage;       // ボタン電池電圧[mV]
} tsAppUart;

// Defines
#define LED1 12
#define LED2 13

// Global Variables
byte u8MyAddr = 0x78;              // 自分の論理デバイスID
byte u8DistAddr1 = 0x01;           // 送信元1の論理デバイスID
byte u8DistAddr2 = 0x02;           // 送信元2の論理デバイスID
String m_strReadSerial;            // シリアル結果格納文字列

void setup() {
  //TWELITEとのシリアル通信
  Serial.begin(38400);
  Serial.print("MONOWIRELESS Serial Parser v");
  Serial.println("");
  Serial.setTimeout(100);
  //通知LED設定
  pinMode( LED1, OUTPUT );
  digitalWrite( LED1, LOW );
  pinMode( LED2, OUTPUT );
  digitalWrite( LED2, LOW );
}

void loop() {
  // MONOWIRELESS 解釈に成功したら終了
  if (bTWELITEDataArrived()) {
    // 系列を読み出せたので構造体に代入する
      Serial.println("Data recieved.");

    // 系列を読み出せたので表示
    tsAppUart pAppUart = tsParseSerialData();

    // 送られてきたパケットの送信元が一緒だったらLEDの状態を変化させる
    if(pAppUart.LID == u8DistAddr1 && (pAppUart.Status == 1 || pAppUart.Status == 2)){
      Serial.println("1 Close");
      digitalWrite( LED1, HIGH );
    }else if(pAppUart.LID == u8DistAddr2 && (pAppUart.Status == 1 || pAppUart.Status == 2)){
      Serial.println("2 Close");
      digitalWrite( LED2, HIGH );
    }else if(pAppUart.LID == u8DistAddr1 && pAppUart.Status == 0 ){
      digitalWrite( LED1, LOW );
    }else if(pAppUart.LID == u8DistAddr2 && pAppUart.Status == 0 ){
      digitalWrite( LED2, LOW );
    }else if(pAppUart.Status == 129){
    }
  }
}

// 受信したパケットを解釈し、構造体に入れる関数
tsAppUart tsParseSerialData()
{
  /*受信パケット構造
原文 : 80000000 C0 0C06 810C5777 01 80 81 03 11 30 08 02 093D 11 30 01 02 0328 00 00 00 01 00 E7 66
1桁 : 01234567 89 0123 45678901 23 45 67 89 01 23 45 67 8901 23 45 67 89 0123 45 67 89 01 23 45 67
2桁 : 0           1          2              3             4              5             6
意味 :          LQI     ID       ID                      電圧              電圧             結果
  */
  tsAppUart sAppUart;
  //通信品質
  String strLQI = m_strReadSerial.substring(8,10);
  Serial.print("LQI = ");
  Serial.println(strLQI);

  sAppUart.LQI = ConvertHEXtoDEC(strLQI);
  Serial.println(sAppUart.LQI);

  //送信元論理ID
  String strLogicID = m_strReadSerial.substring(22,24);
  Serial.print("ID = ");
  Serial.println(strLogicID);

  sAppUart.LID = ConvertHEXtoDEC(strLogicID);
  Serial.println(sAppUart.LID);

  //電圧
  String strVoltage = m_strReadSerial.substring(38,42);
  Serial.print("V = ");
  Serial.println(strVoltage);

  sAppUart.Voltage = ConvertHEXtoDEC(strVoltage);
  Serial.println(sAppUart.Voltage);

  //結果
  String strStatus = m_strReadSerial.substring(62,64);
  Serial.print("Status = ");
  Serial.println(strStatus);

  sAppUart.Status = ConvertHEXtoDEC(strStatus);
  Serial.println(sAppUart.Status);
  Serial.println("");

  return sAppUart;
}

bool bTWELITEDataArrived(){
  // TWELITEからの出力を確認する。
  m_strReadSerial = "";
  if(Serial.available()){
    m_strReadSerial = Serial.readStringUntil(char(':'));
    // DEBUG
    if(m_strReadSerial.length() > 0){
      Serial.print("RawSerial = ");
      Serial.print(m_strReadSerial);
    }

    if(m_strReadSerial.length() == 70){
      return true;
    }
  }
  return false;
}

int ConvertHEXtoDEC(String strHEX){
  // 16進数文字列を10進数に変換
  int intStringLength = strHEX.length() * 4;
  char arChar[intStringLength];
  strHEX.toCharArray(arChar,intStringLength);
  int intResult;
  sscanf(arChar, "%x", &intResult); 
  return intResult;
}

コード説明

上記がコードとなります。一応公式からC++用のライブラリが提供されているのですが、今回は利用しません。
setup関数内でシリアル通信を開始すれば、TWILITE DIPからの通信を受信できます。速度は 38400bpsを指定していますが、これはTWELITE側から変更できます。前回説明したインタラクティブモードから設定が可能です。
通信を開始したらloop関数内で受信した文字列を取得します。

まず bTWELITEDataArrived関数内で必要な文字列数があるか(今回は70文字)確認し、そろっていたらシリアル通信用のメモリから文字列を取得します。

取得した文字列をtsParseSerialData関数に渡しデータをデコードします。その際に16進数だと使いにくいので都度10進数に変換して構造体tsAppUartに格納していきます。

解読に成功したら最後にLED点灯or消灯の条件分岐に入ります。デコード内にはどのTWILITE PALから通信が届いたかが記載されている論理IDが振られています。(標準だとすべてに01が振られているので、複数台使用したシステムを組む場合はPALをPCにつなぎインタラクティブモードに入って論理IDを振り直してください。)
今回は2台利用しますので、どこから届いたかで条件分岐します。
そのあとpAppUart.Statusが1か2であるなら、リードスイッチの近くに磁石があることを示しますので、これは扉が閉じていることを表します。
逆に0である場合は近くに磁石がないことを示していますので、開状態であることが分かります。

この条件を用いて閉の時にLEDがつながっているデジタルピンをHIGHにします。逆の場合はLOWにすることで
閉でLED点灯
開でLED消灯
を表すことができます。

シリアル通信結果

コードをArduinoに配置すればLEDでも状態が分かりますが、シリアル通信画面でもデータが確認できます。
注意点として、ArduinoのRXにTWELITEのTXをつないだ状態ではArduinoにコードを配置できませんので、ご注意ください。
正しく配置ができ、TWELITE PALの設定がちゃんとできていれば、ArduinoIDEのシリアルモニタから上記のような通信が確認できます。
シリアル通信の信頼性を高めるにはもう少しコードの工夫が必要なのですが、データを受信できないことがあっても間違った開閉状態を通知する可能性は非常に低いので、今回は受信文字列長のみの確認という簡単なコードにしました。

まとめ

part3にてpart1にて構想したシステムを実現できました。
現在4ヶ月ほど稼働していますが、大きな問題はなさそうです。
仕様はおおむね満たしているので、満足度は高いのですが、せっかくシリアル通信で実装したので、他のSBCにつないでLINEに開閉状態通知をお知らせするシステムまで作りこんでみたいと思います。
これはまだ出来上がってないので、気長にお待ちください。

part1-システム概要- 
part2-TWELITEとは?使い方を解説-

part3- 通知LED点灯-