# PAL\_MOT-oneshot

[PAL\_MOT](https://mwx.twelite.info/v0.1.3/act_samples/pal_mot)アクトでは連続的に加速度データを取得して都度無線送信していました。このアクトではスリープ復帰後に数サンプル加速度データを取得しそのデータを送ります。

{% hint style="success" %}
このアクトの解説の前に[PAL\_MOT](https://mwx.twelite.info/v0.1.3/act_samples/pal_mot)のアクトの解説をご覧ください。
{% endhint %}

本サンプルは、収録バージョンによって差が大きいため本ページでは２つの解説を記載します。

* [v2 ... 状態変数を用いた loop() 実装に書き換え](#akutono-v-2)
* [初版 ... MWSDK2020\_05 版の SDK 添付](#akutono)

## アクトの解説 (v2)

{% hint style="warning" %}
※ 最新のコードは「[サンプルアクト＞最新版の入手」](https://mwx.twelite.info/v0.1.3/act_samples/..#no)を参照ください。
{% endhint %}

起床→加速度センサーの取得開始→加速度センサーのFIFO割り込み待ち→加速度センサーのデータの取り出し→無線送信→スリープという

起床→加速度センサーの取得開始→加速度センサーのFIFO割り込み待ち→加速度センサーのデータの取り出し→無線送信→スリープという流れになります。

{% hint style="info" %}
加速度センサーは、FIFOキューが一杯になるとFIFOキューへのデータ追加を停止します。
{% endhint %}

### 状態変数

```cpp
enum class E_STATE {
	INIT = 0,
	START_CAPTURE,
	WAIT_CAPTURE,
	REQUEST_TX,
	WAIT_TX,
	EXIT_NORMAL,
	EXIT_FATAL
} eState;
```

列挙体として `eState` 変数を宣言しています。

### begin()

```cpp
void begin() { 
	// sleep immediately, waiting for the first capture.
	sleepNow();
}
```

`setup()`を終了した後に呼ばれます。ここでは初回スリープを実行しています。

### wakeup()

```cpp
void wakeup() {
	Serial << crlf << "--- PAL_MOT(OneShot):" 
	       << FOURCHARS << " wake up ---" << crlf;
	eState = E_STATE::INIT;
}
```

起床後は状態変数`eState`を初期状態INITにセットしています。この後`loop()`が実行されます。

### loop()

```cpp
void loop() {
	auto&& brd = the_twelite.board.use<PAL_MOT>();
	bool loop_more;
	do {
	  loop_more = false;
	  switch(eState) {
 	    ...
	  }
	} while(loop_more);
```

`loop()` の基本構造は状態変数`eState`によるswitch ... case節です。eStateの初期状態はINITです。`loop_more`は状態変数を書き換えた直後、`loop()`を抜ける前にもう一度実行したいときに`true`にセットします。

以下では各case節を解説します。`eState`の初期状態はINITです。

```cpp
			case E_STATE::INIT:
				brd.sns_MC3630.get_que().clear(); // clear queue in advance (just in case).
				loop_more = true;
				eState = E_STATE::START_CAPTURE;
			break;
```

状態INITでは、初期化（結果格納用のキューのクリア）を行います。

```cpp
			case E_STATE::START_CAPTURE:
				u32tick_capture = millis();
				brd.sns_MC3630.begin(
					// 400Hz, +/-4G range, get four samples (can be one sample)
					SnsMC3630::Settings(
						SnsMC3630::MODE_LP_400HZ, SnsMC3630::RANGE_PLUS_MINUS_4G, 4)); 
				eState = E_STATE::WAIT_CAPTURE;
			break;
```

状態START\_CAPTUREでは、MC3630センサーのFIFO取得を開始します。ここでは400Hzで4サンプル取得できた時点でFIFO割り込みが発生する設定にしています。タイムアウトのチェックのため、開始時点のシステム時刻を`u32tick_capture`に格納します。

```cpp
			case E_STATE::WAIT_CAPTURE:
				if (brd.sns_MC3630.available()) {
					brd.sns_MC3630.end(); // stop now!
					eState = E_STATE::REQUEST_TX; loop_more = true;
				} else if ((millis() - u32tick_capture) > 100) {
					Serial << crlf << "!!!FATAL: SENSOR CAPTURE TIMEOUT.";
					eState = E_STATE::EXIT_FATAL;
				}
			break;
```

状態WAIT\_CAPTUREでは、FIFO割り込みを待ちます。割り込みが発生し結果格納用のキューにデータが格納されると`sns_MC3630.available()`が`true`になります。

タイムアウトした場合は状態EXIT\_FATALに遷移します。

```cpp
			case E_STATE::REQUEST_TX:
				u32tick_tx = millis();
				txid = TxReq();
				if (txid) {
					eState = E_STATE::WAIT_TX;
				} else {
					Serial << crlf << "!!!FATAL: TX REQUEST FAILS.";
					eState = E_STATE::EXIT_FATAL;
				}
			break;
```

状態REQUEST\_TXではローカル定義関数TxReq()を呼び出し、得られたセンサーデータの処理と送信パケットの生成、そうし尿級を行います。タイムアウトのチェックのため、開始時点のシステム時刻を`u32tick_tx`に格納します。

```cpp
			case E_STATE::WAIT_TX:
				if(the_twelite.tx_status.is_complete(txid.get_value())) {
					eState = E_STATE::EXIT_NORMAL; loop_more = true;
				} else if (millis() - u32tick_tx > 100) {
					Serial << crlf << "!!!FATAL: TX TIMEOUT.";
					eState = E_STATE::EXIT_FATAL;
				}
			break;
```

状態WAIT\_TXでは、無線パケットの送信完了を待ちます。

タイムアウト時には状態EXIT\_FATALに遷移します。

```cpp
			case E_STATE::EXIT_NORMAL:
				sleepNow();
			break;

			case E_STATE::EXIT_FATAL:
				Serial << flush;
				the_twelite.reset_system();
			break;
```

一連の動作が完了したときは状態EXIT\_NORMALに遷移しローカル定義の関数`sleepNow()`を呼び出しスリープを実行します。またエラーを検出した場合は状態EXIT\_FATALに遷移し、システムリセットを行います。

### MWX\_APIRET TxReq()

この関数では、センサーより得られたサンプル値の取得と、複数サンプルの平均値の計算、

```cpp
int32_t x = 0, y = 0, z = 0;
for (auto&& v: brd.sns_MC3630.get_que()) {
	x += v.x;
	y += v.y;
	z += v.z;
}
x /= brd.sns_MC3630.get_que().size();
y /= brd.sns_MC3630.get_que().size();
z /= brd.sns_MC3630.get_que().size();
```

取得サンプルの平均値を計算します。

{% hint style="info" %}
ここでは除算を行っていますが、TWELITEマイコンには除算回路がないため、計算時間を要する演算となります。例えば以下のような改良が考えられます。

* サンプル数を２のべき乗として、その数を変数に入れず直接指定した除算を行う（ビットシフトによる演算に最適化されます）。
* 平均値を求めず、合計値とサンプル数を送り、受信先で計算する。
  {% endhint %}

```cpp
auto&& x_minmax = std::minmax_element(
	get_axis_x_iter(brd.sns_MC3630.get_que().begin()),
	get_axis_x_iter(brd.sns_MC3630.get_que().end()));

brd.sns_MC3630.get_que().clear(); // clean up the queue
```

X軸の最大値と最小値を計算します。

ここではイテレータと`std::minmax_element()`アルゴリズムを用いて計算します。`get_axis_x_iter`はキューのイテレータをパラメータとして、`axis_xyzt`構造体の`.x`にアクセスするものです。

{% hint style="info" %}
C++ Standard Template Library のアルゴリズムを使用する例として`std::mimmax_element`紹介していますが、上述のforループ内で最大、最小を求めても構いません。
{% endhint %}

ここでキューをクリア`.sns_MC3630.get_que().clear()`しています。

```cpp
	// prepare tx packet
	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 (0x1 send two times in total)
			<< tx_packet_delay(0, 0, 2); // send packet w/ delay
		
		// prepare packet (first)
		pack_bytes(pkt.get_payload() // set payload data objects.
				, make_pair(FOURCHARS, 4)  // just to see packet identification, you can design in any.
				, uint16_t(x)
				, uint16_t(y)
				, uint16_t(z)
				, uint16_t(*x_minmax.first)  // minimum of captured x
				, uint16_t(*x_minmax.second) // maximum of captured x
			);

		// perform transmit
		ret = pkt.transmit();

		if (ret) {
			Serial << "..txreq(" << int(ret.get_value()) << ')';
		}
```

最期にパケットの生成と送信を要求を行います。パケットには X, Y, Z 軸の加速度、X軸の最小値,Y軸の最小値を含めています。

## アクトの解説 (初版)

{% hint style="warning" %}
MWSDK2020\_05 版の SDK 添付のサンプルコードです。&#x20;

※ 最新のコードは「[サンプルアクト＞最新版の入手」](https://mwx.twelite.info/v0.1.3/act_samples/..#no)を参照ください。
{% endhint %}

起床→加速度センサーの取得開始→加速度センサーのFIFO割り込み待ち→加速度センサーのデータの取り出し→無線送信→スリープという流れになります。

{% hint style="info" %}
加速度センサーは、FIFOキューが一杯になるとFIFOキューへのデータ追加を停止します。
{% endhint %}

### wakeup()

起床後加速度センサーを稼働させます。

```cpp
void wakeup() {
	Serial << mwx::crlf << "--- PAL_MOT(OneShot):" << FOURCHARS << " wake up ---" << mwx::crlf;
	auto&& brd = the_twelite.board.use<PAL_MOT>();

	brd.sns_MC3630.get_que().clear(); // clear queue in advance (just in case).
	brd.sns_MC3630.begin(SnsMC3630::Settings(
			SnsMC3630::MODE_LP_400HZ, SnsMC3630::RANGE_PLUS_MINUS_4G, 4)); 
				// 400Hz, +/-4G range, get four samples (can be one sample)

	b_transmit = false;
	txid = 0xFFFF;
}
```

加速度センサーの結果を保存するキューの内容を抹消(`.sns_MC3630.get_que().clear()`)しておきます。加速度センサーのサンプルの取得忘れがあったり、また停止させるまでに次のサンプルが取得したような場合を想定します。

ここで加速度センサーを都度開始します。設定は400Hz,±4G,FIFO割り込みは4サンプルとしています。4サンプルも必要ない場合は1サンプルの設定でも構いません。

### loop()

```cpp
void loop() {
	auto&& brd = the_twelite.board.use<PAL_MOT>();

	if (!b_transmit) {
		if (brd.sns_MC3630.available()) {
			brd.sns_MC3630.end(); // stop now!

			Serial << "..finish sensor capture." << mwx::crlf
				<< "  ct=" << int(brd.sns_MC3630.get_que().size());

			// get all samples and average them.
			int32_t x = 0, y = 0, z = 0;
			for (auto&& v: brd.sns_MC3630.get_que()) {
				x += v.x;
				y += v.y;
				z += v.z;
			}
			x /= brd.sns_MC3630.get_que().size();
			y /= brd.sns_MC3630.get_que().size();
			z /= brd.sns_MC3630.get_que().size();

			Serial << format("/ave=%d,%d,%d", x, y, z) << mwx::crlf;

			// just see X axis, min and max
			//auto&& x_axis = get_axis_x(brd.sns_MC3630.get_que());
			//auto&& x_minmax = std::minmax_element(x_axis.begin(), x_axis.end());
			auto&& x_minmax = std::minmax_element(
				get_axis_x_iter(brd.sns_MC3630.get_que().begin()),
				get_axis_x_iter(brd.sns_MC3630.get_que().end()));

			brd.sns_MC3630.get_que().clear(); // clean up the queue

			// prepare tx packet
			if (auto&& pkt = nwk.the_twelite.network.use<NWK_SIMPLE>()) {
				auto&& pkt = nwk.prepare_tx_packet(); // get new packet instance.

				// 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 (0x1 send two times in total)
					<< tx_packet_delay(0, 0, 2); // send packet w/ delay
				
				// prepare packet (first)
				pack_bytes(pkt.get_payload() // set payload data objects.
						, make_pair(FOURCHARS, 4)  // just to see packet identification, you can design in any.
						, uint16_t(x)
						, uint16_t(y)
						, uint16_t(z)
						, uint16_t(*x_minmax.first)  // minimum of captured x
						, uint16_t(*x_minmax.second) // maximum of captured x
					);

				// perform transmit
				MWX_APIRET ret = pkt.transmit();
				
				if (ret) {
					Serial << "..txreq(" << int(ret.get_value()) << ')';
					txid = ret.get_value() & 0xFF;
				} else {
					sleepNow();
				}
				
				// finished tx request
				b_transmit = true;
			}
		}
	} else {
		// wait until transmit completion.
		if(the_twelite.tx_status.is_complete(txid)) {
			sleepNow();
		}
	}
}

```

このアクトでは、サンプル取得後、すぐに加速度センサーの動作を停止します。

```cpp
if (brd.sns_MC3630.available()) {
	brd.sns_MC3630.end(); // stop now!
```

取得サンプルの平均値を計算します。

```cpp
int32_t x = 0, y = 0, z = 0;
for (auto&& v: brd.sns_MC3630.get_que()) {
	x += v.x;
	y += v.y;
	z += v.z;
}
x /= brd.sns_MC3630.get_que().size();
y /= brd.sns_MC3630.get_que().size();
z /= brd.sns_MC3630.get_que().size();
```

X軸の最大値と最小値を計算します。

```cpp
auto&& x_minmax = std::minmax_element(
	get_axis_x_iter(brd.sns_MC3630.get_que().begin()),
	get_axis_x_iter(brd.sns_MC3630.get_que().end()));

brd.sns_MC3630.get_que().clear(); // clean up the queue
```

ここではイテレータと`std::minmax_element()`アルゴリズムを用いて計算します。`get_axis_x_iter`はキューのイテレータをパラメータとして、`axis_xyzt`構造体の`.x`にアクセスするものです。

最後にキューをクリア`.sns_MC3630.get_que().clear()`しています。
