Slp_Wk_and_Tx

Slp_Wk_and_Tx は、定期起床後、何か実行(センサーデータの取得など)を行って、その結果を無線パケットとして送信するようなアプリケーションを想定した、テンプレートソースコードです。

setup(), loop() の形式では、どうしても loop() 中が判読しづらい条件分岐が発生しがちです。本Actでは、loop()中を switch構文による単純な状態遷移を用いることで、コードの見通しを良くしています。

このアクトの解説の前にBRD_APPTWELITEの解説をご覧いただくことを推奨します。

TWELITE STAGE 2020_05 には収録されていません。以下のリンク(GitHub)より入手ください。

https://github.com/monowireless/Act_samples

アクトの機能

  • 起動後、速やかにスリープする

    1. setup() : 初期化する

    2. begin() : スリープに遷移する

  • スリープ起床後、状態変数を初期化し、以下の順に動作を行う

    1. wakeup(): スリープからの起床、各初期化を行う

    2. loop()状態INIT->WORK_JOBに遷移: 何らかの処理を行う(このActでは 1ms ごとの TickCount ごとにカウンタを更新し 100 カウント後にTX状態に進む)

    3. loop() 状態TX: 送信要求を行う

    4. loop() 状態WAIT_TX: 送信完了待ちを行う

    5. loop() 状態EXIT_NORMAL: スリープする (1. に戻る)

  • loop() 状態EXIT_FATAL: エラーが発生した場合は、モジュールリセットする

アクトの解説

インクルード

#include <TWELITE>
#include <NWK_SIMPLE>
#include "Common.h"

パケット送信を行うため <NWK_SIMPLE> をインクルードしています。また、アプリケーションIDなど基本的な定義は "Common.h" に記述しています。

Common.h には基本的な定義に加え、以下の列挙体が定義されています。こちらを状態変数として利用します。

enum class E_STATE {
INIT = 0, // INIT STATE
WORK_JOB, // do some job (e.g sensor capture)
TX, // reuest transmit
WAIT_TX, // wait its completion
EXIT_NORMAL, // normal exiting.
EXIT_FATAL // has a fatal error (will do system reset)
};

setup()

void setup() {
/*** SETUP section */
txreq_stat = MWX_APIRET(false, 0);
// the twelite main class
the_twelite
<< TWENET::appid(APP_ID) // set application ID (identify network group)
<< TWENET::channel(CHANNEL) // set channel (pysical channel)
<< TWENET::rx_when_idle(false); // open receive circuit (if not set, it can't listen packts from others)
// Register Network
auto&& nwk = the_twelite.network.use<NWK_SIMPLE>();
nwk << NWK_SIMPLE::logical_id(DEVICE_ID); // set Logical ID.
/*** BEGIN section */
the_twelite.begin(); // start twelite!
/*** INIT message */
Serial << "--- Sleep an Tx Act ---" << crlf;
}

the_tweliteクラスオブジェクトの初期化とネットワーク <NWK_SIMPLE> の登録を行います。

begin()

void begin() {
Serial << "..begin (run once at boot)" << crlf;
SleepNow();
}

setup()の直後に一度だけ呼び出されます。SleepNow()関数夜を呼び出して初回のスリープ手続きを行います。

wakeup()

void wakeup() {
Serial << crlf << int(millis()) << ":wake up!" << crlf;
eState = E_STATE::INIT;
}

起床直後に呼び出されます。ここでは状態変数eStateを初期状態E_STATE::INITにセットします。この後loop()が呼び出されます。

loop()

void loop() {
bool loop_more; // if set, one more loop on state machine.
do {
loop_more = false; // set no-loop at the initial.
switch(eState) {
case E_STATE::INIT:
eState = E_STATE::WORK_JOB; loop_more = true;
break;
case E_STATE::WORK_JOB:
if (TickTimer.available())
if(--dummy_work_count == 0)
eState = E_STATE::TX: loop_more = true;
break;
case E_STATE::TX:
txreq_stat = vTransmit();
if(txreq_stat) {
u32millis_tx = millis();
eState = E_STATE::WAIT_TX;
} else {
eState = E_STATE::EXIT_FATAL; loop_more = true;
}
break;
case E_STATE::WAIT_TX:
if (the_twelite.tx_status.is_complete(txreq_stat.get_value())) {
eState = E_STATE::EXIT_NORMAL; loop_more = true;
} else if (millis() - u32millis_tx > 100) {
eState = E_STATE::EXIT_FATAL; loop_more = true;
}
break;
case E_STATE::EXIT_NORMAL:
SleepNow();
break;
case E_STATE::EXIT_FATAL:
the_twelite.reset_system();
break;
}
} while (loop_more);

上記のコードは、実際のコードを簡略化したものです。

このコードではloop_moreを条件としてdo..while() 構文のループになっています。これはloop()を脱出せずに次の状態のコード(case節)を実行する目的です。

wakeup()でINIT状態にセットされていますので初回のloop()ではINITが評価されます。ここは変数の初期化しているだけです。続く状態の処理を行うのにloop()を脱出する必要がないためloop_moretrueにしています。

WORK_JOB状態ではTichTimer.available()になるたびにカウンタを減算し0になったら次の状態TXに遷移します。

TX状態ではvTransmit()関数を呼び出しパケット送信要求を行います。この時点ではまだパケットが送信されていないため、この時点でスリープをしてはいけません。パケット送信要求が成功したらWAIT_TX状態に遷移します。タイムアウトの管理のため、この時点でのシステム時間u32millis_txを保存します。失敗したらEXIT_FATALに遷移します(モジュールのリセット実行)。

WAIT_TX状態では送信IDに対応する送信が完了したかを判定し、完了したらEXIT_NORMAL、100ms以上経過したらEXIT_FATAL状態に遷移します。

EXIT_NORMAL状態はSleepNow()を呼び出し、スリープします。

EXIT_FATAL状態ではシステムリセットを行います。

void SleepNow()

void SleepNow() {
uint16_t u16dur = SLEEP_DUR;
u16dur = random(SLEEP_DUR - SLEEP_DUR_TERMOR, SLEEP_DUR + SLEEP_DUR_TERMOR);
Serial << int(millis()) << ":sleeping for " << int(u16dur) << "ms" << crlf << mwx::flush;
the_twelite.sleep(u16dur, false);
}

周期スリープを行います。スリープ時間はrandom()関数を用いて、一定の時間ブレを作っています。これは複数のデバイスの送信周期が同期した場合、著しく失敗率が上がる場合があるためです。

MWX_APIRET vTransmit()

MWX_APIRET vTransmit() {
Serial << int(millis()) << ":vTransmit()" << crlf;
if (auto&& pkt = the_twelite.network.use<NWK_SIMPLE>().prepare_tx_packet()) {
// set tx packet behavior
pkt << tx_addr(0x00) // 0..0xFF (LID 0:parent, FE:child w/ no id, FF:LID broad cast), 0x8XXXXXXX (long address)
<< tx_retry(0x1) // set retry (0x3 send four times in total)
<< tx_packet_delay(0,0,2); // send packet w/ delay (send first packet with randomized delay from 0 to 0ms, repeat every 2ms)
// prepare packet payload
pack_bytes(pkt.get_payload() // set payload data objects.
, make_pair(FOURCC, 4) // string should be paired with length explicitly.
, uint32_t(millis()) // put timestamp here.
);
// do transmit
//return nwksmpl.transmit(pkt);
return pkt.transmit();
}
return MWX_APIRET(false, 0);
}

ID=0x00の親機宛に無線パケットの送信要求を行います。格納されるデータはActサンプルで共通に使われている4文字識別子(FOURCC)に加え、システム時間[ms]が含まれます。

MWX_APIRETuint32_t型をラップしたクラスで、MSBを失敗成功のフラグとし、以下31ビットをデータとして用いています。pkt.transmit()の戻り型になっており、送信要求の成功と失敗ならびに送信IDをデータ部に格納します。