# Slp\_Wk\_and\_Tx

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

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

{% hint style="success" %}
このアクトの解説の前に[BRD\_APPTWELITEの解説](https://mwx.twelite.info/v0.1.3/act_samples/brd_apptwelite)をご覧いただくことを推奨します。
{% endhint %}

{% hint style="info" %}
TWELITE STAGE 2020\_05 には収録されていません。以下のリンク（GitHub)より入手ください。

<https://github.com/monowireless/Act_samples>
{% endhint %}

## アクトの機能

* 起動後、速やかにスリープする
  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: エラーが発生した場合は、モジュールリセットする

## アクトの解説

### インクルード

```cpp
#include <TWELITE>
#include <NWK_SIMPLE>

#include "Common.h"
```

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

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

```cpp
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()

```cpp
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()

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

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

### wakeup()

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

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

### loop()

```cpp
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()

```cpp
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()

```cpp
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サンプルで共通に使われている４文字識別子(`FOURCC`)に加え、システム時間\[ms]が含まれます。

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