Slp_Wk_and_Tx は、定期起床後、何か実行(センサーデータの取得など)を行って、その結果を無線パケットとして送信するようなアプリケーションを想定した、テンプレートソースコードです。
setup(), loop()
の形式では、どうしても loop()
中が判読しづらい条件分岐が発生しがちです。本Actでは、loop()
中を switch構文による単純な状態遷移を用いることで、コードの見通しを良くしています。
アクトの機能
スリープ起床後、状態変数を初期化し、以下の順に動作を行う
wakeup()
: スリープからの起床、各初期化を行う
loop()
状態INIT->WORK_JOBに遷移: 何らかの処理を行う(このActでは 1ms ごとの TickCount ごとにカウンタを更新し 100 カウント後にTX状態に進む)
loop()
状態WAIT_TX: 送信完了待ちを行う
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_more
をtrue
にしています。
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_APIRET
はuint32_t
型をラップしたクラスで、MSBを失敗成功のフラグとし、以下31ビットをデータとして用いています。pkt.transmit()
の戻り型になっており、送信要求の成功と失敗ならびに送信IDをデータ部に格納します。