ぱぷりかの機械系技術ノート

なにかのお役に立てばうれしいです

TWELITEとArduinoを使った測定データの収集について8(Arduino用ソースコード)

前回のハードウェア構成に対するArduinoのスケッチについて考える.

このブログ上ではID=0d42のエンドデバイスのスケッチのみ示し,その他のスケッチはGitHubに上げることにする(こちら). スケッチ中に説明が入っているので,やっていることはすぐわかると思う. 中継デバイスでもほとんど同じコードが利用できるようになっている. なお,『■■』の箇所はデバイスごとに見直しが必要な箇所である.

enddevice_0d42.ino

メインとなるスケッチ.関数acquire内のコードを書き換えることで,様々な測定対象に対応できる.

#include <SoftwareSerial.h>
#include "peripheral.h"
#include "handler.h"

static const unsigned long MeasInterval = 3000;  // ■■測定間隔
SoftwareSerial mySerial(RX_PIN, TX_PIN);
static unsigned long previousMillis = 0;
static char rxMessage[RX_HEADER_LEN + MAX_CONTENT_LEN + 3] = "";
static int rxMsgLength;

void acquire(int *measValue);
void transmit(const int *measValue);
bool parseMessage(MessageHandler *pHandler, const char *rxMessage);

RxMsgHandler ignoreHandler = {{handleIgnore}, 0, 0};
RxMsgHandler normalMsgHandler = {{handleNormalMsg}, NORMAL_ID, MAX_CONTENT_LEN};
ChainedHandler normalMsgChain = {{isHandled}, &normalMsgHandler.base, NULL};
ChainedHandler ignoreChain = {{isHandled}, &ignoreHandler.base, &normalMsgChain.base};

void setup() {
  pinMode(LED_PIN, OUTPUT);
  Serial.begin(9600);
  while (!Serial) yield();
  mySerial.begin(9600);
}

void loop() {
  // 一定時間おきにデータを測定して,送信するための処理
  unsigned long currentMillis = millis();
  if (currentMillis - previousMillis >= MeasInterval) {
    int measValue[NUM_OF_MEAS_ITEMS];
    acquire(measValue);
    transmit(measValue);
    previousMillis = currentMillis;
  }

  //データを受信した場合の処理
  while (mySerial.available()) {
    char c = mySerial.read();
    
    // 受信した文字が改行コードLFの場合には受信した文字列を解析する
    // LFとCR以外の場合には受信文字をrxMessageに蓄積する
    if (c == 10) {
      rxMessage[rxMsgLength] = '\0';
      Serial.print("RX: ");
      Serial.println(rxMessage);
      if (!parseMessage(&ignoreChain.base, rxMessage))
        Serial.println("Handler: None");
      rxMessage[0] = '\0';
      rxMsgLength = 0;
    } else if (c != 13) {
      rxMessage[rxMsgLength] = c;
      rxMsgLength++;
    }
  }
}

// ■■測定値を取得する.戻り値は整数とする
void acquire(int *measValue) {
  for (int i = 0; i < NUM_OF_MEAS_ITEMS; i++)
    measValue[i] = random(0, 10); 
}

// 測定値を文字列としてTWELITEへ送信する
void transmit(const int *measValue) {
  char txMessage[TX_HEADER_LEN + MAX_CONTENT_LEN + 3] = "";

  // 送信文字列のヘッダーの作成
  sprintf(txMessage, ":%02xA0%02xFF%02x",
          DOWNSTREAM_TWELITE, NORMAL_ID, THIS_TWELITE);

  // 送信文字列への測定値の追加
  for (int i = 0; i < NUM_OF_MEAS_ITEMS; i++)
    sprintf(txMessage + TX_HEADER_LEN + 2 + i * MEAS_VALUE_LEN,
            "%04x", measValue[i]);

  // 送信文字列へのChecksumの追加
  sprintf(txMessage + TX_HEADER_LEN + 2 + NUM_OF_MEAS_ITEMS * MEAS_VALUE_LEN,
          "%02x", getChecksumOf(txMessage));

  // 送信実行
  toUpperStr(txMessage);
  writeToSerial(txMessage);
}

// 受信メッセージに応じて,チェインオブレスポンシビリティパターンによる適切な処理を行う
// 処理ができた場合にはtrueを,処理ができなかった場合にはfalseを返す
bool parseMessage(MessageHandler *pHandler, const char *rxMessage) {
  RxContent rxContent;

  // 各種データをrxContentに格納する
  rxContent.message = rxMessage;
  sscanf(rxMessage + 1, "%2x", &rxContent.tweliteId);
  sscanf(rxMessage + 5, "%2x", &rxContent.responseId);
  rxContent.contentLength = strlen(rxMessage) - RX_HEADER_LEN - 2;

  // 受信メッセージの内容に応じて,適切に処理する
  return pHandler->handleMessage(pHandler, &rxContent);
}

peripheral.h

各種設定値を入力するためのヘッダファイル.

#ifndef T_SENSOR_UNIT
#define T_SENSOR_UNIT
#include "util.h"

extern SoftwareSerial mySerial;

// 応答IDの設定
typedef enum {
  NORMAL_ID = 80,  // 0x50
} ResponseId;

// メッセージの長さの設定
typedef enum {
  TX_HEADER_LEN = 9,     // 送信メッセージのヘッダー長
  RX_HEADER_LEN = 29,    // 受信メッセージのヘッダー長
  MEAS_VALUE_LEN = 4,    // 測定値の文字列長
  NUM_OF_MEAS_ITEMS = 2, // ■■測定値の個数
  MAX_CONTENT_LEN = 22,  // NUM_OF_MEAS_ITEMS*MEAS_VALUE_LEN+2
} MessageLength;

// LEDのピンの設定
typedef enum {
  SENSOR_PIN = 0,
  RX_PIN = 6,
  TX_PIN = 7,
  LED_PIN = 2,
} PinSetting;

// TWELITE論理IDの設定
typedef enum {
  THIS_TWELITE = 42,       // ■■このTWELITEの論理ID(10進数)
  DOWNSTREAM_TWELITE = 45, // ■■下流側TWELITEの論理ID(10進数)
  IGNORE_TWELITE = 219,    // 0xDB
} TweliteId;

// 受信メッセージを格納する構造体
typedef struct {
  const char *message; // 受信した文字列
  unsigned int tweliteId;       // 送信元のTWELITEの論理ID
  unsigned int responseId;      // 受信メッセージのヘッダーに含まれている応答ID
  int contentLength;   // HEADERとCHECKSUMを除いた文字列長
} RxContent;
#endif

handler.h

チェインオブレスポンシビリティパターンで使用する構造体等を宣言するためのヘッダファイル.

#ifndef HANDLER_H
#define HANDLER_H

// 引数にMessageHandler型のポインタとRxContent型のポインタをもち,
// bool型の変数を返す関数へのポインタを保持する構造体
typedef struct _MessageHandler {
  bool (* const handleMessage)(struct _MessageHandler *pThis, RxContent *pContent);
} MessageHandler;

// MessageHandlerとそれ以外に必要なデータを格納する構造体
// 擬似的にMessageHandlerを継承した構造体となっている
typedef struct {
  MessageHandler base;
  const unsigned int responseId;
  const int contentLength;
} RxMsgHandler;

// 処理の連鎖を格納するための構造体
// 擬似的にMessageHandlerを継承した構造体となっている
typedef struct {
  MessageHandler base;
  MessageHandler *pWrapped;
  MessageHandler *pNext;
} ChainedHandler;

// 受信メッセージの処理を試みて,処理ができた場合にはtrueを返す
// 処理がができなかった場合には、後続のHandlerを呼び出し,
// 後続のHandlerの戻り値をそのまま呼び出し元に返す
// 後続のHandlerがいない場合にはfalseを返す
bool isHandled(MessageHandler *pThis, RxContent *pContent);

// 不要な受信文字列を無視するHandler
bool handleIgnore(MessageHandler *pThis, RxContent *pContent);

// 通常の受信メッセージを下流側のデバイスに送信するHandler
bool handleNormalMsg(MessageHandler *pThis, RxContent *pContent);
#endif

handler.ino

チェインオブレスポンシビリティパターンで使用する関数を定義するためのスケッチ.

#include "handler.h"
#include "peripheral.h"

bool isHandled(MessageHandler *pHandler, RxContent *pContent) {
  ChainedHandler *pThis = (ChainedHandler *)pHandler;

  pHandler = pThis->pWrapped;
  if (pHandler->handleMessage(pHandler, pContent))
    return true;

  pHandler = pThis->pNext;
  if (pHandler == NULL)
    return false;
  
  return pHandler->handleMessage(pHandler, pContent);
}

bool handleIgnore(MessageHandler *pHandler, RxContent *pContent) {
  // 下記の条件の場合には即退出
  if (pContent->tweliteId != IGNORE_TWELITE
      && pContent->contentLength <= MAX_CONTENT_LEN)
      return false;

  // 何もせず終了
  Serial.println("Handler: Ignore");
  return true;
}

bool handleNormalMsg(MessageHandler *pHandler, RxContent *pContent) {
  RxMsgHandler *pThis = (RxMsgHandler *)pHandler;

  // 下記の条件の場合には即退出
  if (pContent->responseId != pThis->responseId)
      return false;
  if ((pContent->contentLength % MEAS_VALUE_LEN) != 2)
      return false;

  char txMessage[TX_HEADER_LEN + MAX_CONTENT_LEN + 3];
  sprintf(txMessage, ":%02xA0%02xFF", DOWNSTREAM_TWELITE, pThis->responseId);
  for (int i = 0; i < pContent->contentLength; i++)
    txMessage[i + TX_HEADER_LEN] = rxMessage[i + RX_HEADER_LEN];
  txMessage[TX_HEADER_LEN + pContent->contentLength] = '\0';
  sprintf(txMessage + (TX_HEADER_LEN + pContent->contentLength),
          "%02x", getChecksumOf(txMessage));
          
  toUpperStr(txMessage);
  writeToSerial(txMessage);
  
  Serial.println("Handler: NormalMessage");
  return true;
}

util.h

その他の関数を定義するためのヘッダファイル.

#ifndef UTIL_H
#define UTIL_H
#include "peripheral.h"

// チェックサムを計算するための関数
int getChecksumOf(const char *);

// 大文字に変換するための関数
void toUpperStr(const char *);

// 標準のシリアルポートとSoftwareSerialで擬似的に作成された
// シリアルポートの両方に書き込みを行うための関数
void writeToSerial(const char *);

#endif

util.ino

その他の関数を定義するためのスケッチ.

#include "util.h"

int getChecksumOf(const char *message) {
  char str[3];
  int checksum = 0;
  byte msgLength = strlen(message);
  for (int i = 1; i < msgLength; i = i + 2)
  {
    str[0] = message[i];
    str[1] = message[i + 1];
    str[2] = '\0';
    checksum += strtol(str, NULL, 16);
  }
  return 256 - (checksum % 256);
}

void toUpperStr(const char *message) {
  char *p = (char *)message;
  while (*p)
  { 
    *p = toupper(*p);
    p++;
  }
}

void writeToSerial(const char *txMessage) {
  digitalWrite(LED_PIN, HIGH);
  Serial.print("TX: ");
  Serial.println(txMessage);
  mySerial.println(txMessage);
  digitalWrite(LED_PIN, LOW);
}

コーディングに際して,花井志生著『モダンC言語プログラミング』(KADOKAWA,2013年)を参考にした.