/      日本語

Slp_Wk_and_Tx

Transmits a packet upon waking from sleep
Slp_Wk_and_Tx is a template source code intended for applications that perform some execution (such as sensor data acquisition) after periodic wake-up, and transmit the result as a wireless packet.

In the form of setup() and loop(), complex conditional branches tend to occur in loop(), making it difficult to read. In this act, the code readability is improved by using the SM_SIMPLE state machine with a simple _switch_ syntax for state transitions inside loop().

Functionality of the Act

  • After startup, go through initialization and then sleep once
    1. Initialize in setup()
    2. Execute sleep in begin()
  • After waking from sleep, initialize state variables and perform the following steps in order:
    1. wakeup() wakes from sleep and performs initialization
    2. loop() transitions state from INIT to WORK_JOB: performs some processing (in this act, updates a counter every 1ms TickCount and proceeds to TX state after a random count)
    3. loop() state TX requests transmission
    4. loop() state WAIT_TX waits for transmission completion
    5. loop() state EXIT_NORMAL goes to sleep (returns to 1.)
  • If an error occurs, loop() state EXIT_FATAL resets the module

Explanation of the Act

Declaration Section

Includes

#include <TWELITE>
#include <NWK_SIMPLE>
#include <SM_SIMPLE>

#include "Common.h"

<NWK_SIMPLE> is included to perform packet transmission. Basic definitions such as application ID are described in "Common.h".

State Definitions

To describe sequential processing inside loop(), this sample uses the concept of a state machine (state transitions). It uses <SM_SIMPLE>, which summarizes very simple state transitions.

The enumeration STATE corresponding to the following states is defined in Common.h.

enum class 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)
};

Declare the SM_SIMPLE state machine (state transitions) using the enumeration STATE that represents states.

SM_SIMPLE<STATE> step;

The declared step includes functions for state management, timeouts, and waiting for processing.

Sensor Data

This sample does not process sensor data but prepares dummy data.

struct {
	uint16_t dummy_work_ct_now;
	uint16_t dummy_work_ct_max;  // counter for dummy work job.
} sensor;

setup()

void setup() {
	/*** SETUP section */
	step.setup(); // init state machine

	// 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;
}

Initializes variables and class objects.

  • Initializes the step state machine
  • Initializes the the_twelite class object
  • Registers and initializes the network <NWK_SIMPLE> (registers DEVICE_ID)

Next, starts the class object and hardware.

the_twelite.begin(); // start twelite!

This procedure starts the_twelite. Although not shown in act0..4, when setting configurations or registering behaviors of the_twelite, always call this.

begin()

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

Called once immediately after setup(). Calls SleepNow() to perform the initial sleep procedure.

wakeup()

void wakeup() {
 memset(&sensor, 0, sizeof(sensor));
	Serial << crlf << int(millis()) << ":wake up!" << crlf;
}

Called immediately after waking up. Here, it initializes the sensor data area and outputs a wake-up message.

loop()

void loop() {
	do {
		switch(step.state()) {
		case STATE::INIT:
			sensor.dummy_work_ct_now = 0;
			sensor.dummy_work_ct_max = random(10,1000);
			step.next(STATE::WORK_JOB);
		break;

		...
		}
	} while (step.b_more_loop());
}

The above code is a simplified version of the actual code.

This control structure uses the SM_SIMPLE state machine. It is a do..while() loop. Inside the loop is a switch case statement that branches processing based on the state obtained from .state(). State transitions are done by calling .next() which writes a new state value to an internal variable in the state machine.

step.b_more_loop() is set to true when a state transition occurs by .next(). This is to execute the next state’s code (case clause) without exiting loop() when a state transition occurs.

The following explains each state.

STATE::INIT

sensor.dummy_work_ct_now = 0;
sensor.dummy_work_ct_max = random(10,1000);

step.next(STATE::WORK_JOB);

Initializes dummy sensor values. One is an increment counter, the other is a randomly determined stop count.

STATE::WORK_JOB

if (TickTimer.available()) {
	Serial << '.';
	sensor.dummy_work_ct_now++;
	if (sensor.dummy_work_ct_now >= sensor.dummy_work_ct_max) {
		Serial << crlf;
		step.next(STATE::TX);
	}
}

In the WORK_JOB state, processing is done at 1ms timer intervals. TickTimer.available() becomes true at each tick timer. The counter is incremented at each tick timer, and when it reaches dummy_work_ct_max, it transitions to the next state STATE::TX.

STATE::TX

if (Transmit()) {
	Serial << int(millis()) << ":tx request success!" << crlf;
	step.set_timeout(100);
	step.clear_flag();
	step.next(STATE::WAIT_TX);
} else {
	// normall it should not be here.
	Serial << int(millis()) << "!FATAL: tx request failed." << crlf;
	step.next(STATE::EXIT_FATAL);
}

Calls the Transmit() function to request packet transmission. If the request succeeds, it transitions to STATE::WAIT_TXEVENT to wait for transmission completion. Here, the timeout and flag functions of the SM_SIMPLE state machine are used for the wait loop (a simple judgment based on variable changes during the wait loop).

A single transmission request failure is usually not expected, but if it fails, it transitions to the exceptional state STATE::EXIT_FATAL.

STATE::WAIT_TX

if (step.is_flag_ready()) {
	Serial << int(millis()) << ":tx completed!" << crlf;
	step.next(STATE::EXIT_NORMAL);
} else if (step.is_timeout()) {
	Serial << int(millis()) << "!FATAL: tx timeout." << crlf;
	step.next(STATE::EXIT_FATAL);
}

Waiting for transmission completion is judged by setting the flag in the state machine function by on_tx_comp() described later. Timeout is judged by calling .is_timeout(), which checks the elapsed time since .set_timeout() was called.

Whether transmission succeeds or fails, a completion notification usually exists, but a timeout is set to transition to the exceptional state STATE::EXIT_FATAL.

STATE::EXIT_NORMAL

SleepNow();

Calls SleepNow() to enter sleep processing.

STATE::EXIT_FATAL

Serial << crlf << "!FATAL: RESET THE SYSTEM.";
delay(1000); // wait a while.
the_twelite.reset_system();

Performs a system reset as a critical error.

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;
	Serial.flush();

	step.on_sleep(); // reset status of statemachine to INIT state.

	the_twelite.sleep(u16dur, false);
}

Performs periodic sleep. The sleep duration is randomized using the random() function to create some jitter. This is because if multiple devices synchronize their transmission cycles, the failure rate may increase significantly.

Before sleeping, the SM_SIMPLE state machine’s state is set to INIT by calling .on_sleep().

Transmit()

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.
			, uint16_t(sensor.dummy_work_ct_now) // put dummy sensor information.
		);

		// do transmit
		//return nwksmpl.transmit(pkt);
		return pkt.transmit();
	}

	return MWX_APIRET(false, 0);
}

Requests wireless packet transmission to the parent device with ID=0x00. The stored data includes the 4-character identifier (FOURCC) commonly used in Act samples, the system time [ms], and the dummy sensor value (sensor.dummy_work_ct_now).

First, obtains an object to store the transmission packet. Operates this object to set transmission data and conditions.

if (auto&& pkt = the_twelite.network.use<NWK_SIMPLE>().prepare_tx_packet()) {

In the MWX library, the object is obtained inside an if statement, and the object’s bool evaluation is used to proceed if true.

Here, the board object is obtained by the_twelite.network.use<NWK_SIMPLE>(), and the packet object is obtained by calling .prepare_tx_packet() on the board object. Failure to obtain the packet object is usually unexpected but occurs if the transmission queue is full and cannot accept transmission requests. This sample only sends a single transmission, so errors are limited to serious unexpected problems.

pkt << tx_addr(0x00) // Destination
		<< tx_retry(0x1) // Retry count
		<< tx_packet_delay(0,0,2); // Transmission delay

Sets transmission conditions (destination, retry, etc.) using the << operator on the obtained pkt object.

tx_addr specifies the packet destination. tx_retry is the retry count. tx_packet_delay specifies transmission delay.

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.
	, uint16_t(sensor.dummy_work_ct_now) // put dummy sensor information.
);

The packet payload (data part) is an array derived from smblbuf<uint8_t> obtained by pkt.get_payload(). You can directly set values to this array, but here the pack_bytes() function is used to set values.

This function takes a variable number of arguments. The first parameter is the array object obtained from .get_payload().

  • make_pair(FOURCC,4): make_pair is from the C++ standard library and creates a std::pair object. For a string type, it means writing 4 bytes from the beginning explicitly. (Strings can be confusing regarding including or excluding the terminator, so here the number of bytes to write is explicitly specified.)
  • Specifying a uint32_t type writes 4 bytes in big-endian order.
  • The same applies for uint16_t data.

Finally, calls .transmit() to request transmission. The return type is MWX_APIRET. After the request, actual transmission occurs, which may take several ms to tens of ms depending on parameters and size. on_tx_comp() is called upon completion.

return pkt.transmit();

on_tx_comp()

void on_tx_comp(mwx::packet_ev_tx& ev, bool_t &b_handled) {
	step.set_flag(ev.bStatus);
}

This system event is called upon transmission completion. Here, .set_flag() is called to mark completion.