Slp_Wk_and_Tx
Slp_Wk_and_Tx is a template source code for an application which, after a regular wake-up, does something (e.g. acquires sensor data) and sends the result as a wireless packet.
In the form of setup()
and loop()
, conditional branches which are difficult to read in loop()
tend to occur. In this Act, we use SM_SIMPLE state machine in loop()
and simple state transition by switch syntax to improve the visibility of the code.
This act includes
The control structure of a typical intermittent operation (sleep -> wake -> measure -> radio transmission -> sleep)
Generation and transmission procedures for outgoing packets and waiting for completion
ACT features.
After starting up, the system goes through an initialization process and then goes to sleep.
setup()
Initialisebegin()
Run sleep
After waking up from sleep, the state variables are initialized and the actions are performed in the following order
wakeup()
wakes up from sleep, performs each initializationloop()/INIT->WORK_JOB state
: does some processing (in this Act, the counter is updated every TickCount for 1ms and moves to TX state after a count determined by a random number)loop()/TX state
: Make a request to sendloop()/WAIT_TX state
: Waiting for transmission completionloop()/EXIT_NORMAL state
: Run sleep (back to 1.)
loop()/EXIT_FATAL state
:Resetting the module if an error occurs
Description of the ACT
Declarations
Includes
#include <TWELITE>
#include <NWK_SIMPLE>
#include <SM_SIMPLE>
#include "Common.h"
To send packets, <NWK_SIMPLE>
is included. Also, basic definitions such as application ID are in "Common.h"
.
State definition.
In order to describe the sequential processing in loop()
, this sample uses the concept of a state machine (state transition). It uses <SM_SIMPLE>
, which summarizes the processing of very simple state transitions.
An enum STATE
is defined in Common.h
for the following states.
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)
};
Declares an SM_SIMPLE state machine (state transition) using the STATE
state enumeration.
SM_SIMPLE<STATE> step;
The step
declared here contains functions for managing state, timeouts and waiting for processing.
Sensor data.
In this sample we do not process the sensor data, but we prepare dummy data for explanation.
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.
Initialization of the
step
state machineInitialization of
the_twelite
class objectRegister and initialize the network
<NWK_SIMPLE>
(registerDEVICE_ID
).
This is followed by the initiation of class objects and hardware.
the_twelite.begin(); // start twelite!
This is the procedure to start the_twelite
, it didn't appear in act0..4, but you should call it if you set up the_twelite
or register various behaviors.
begin()
void begin() {
Serial << "..begin (run once at boot)" << crlf;
SleepNow();
}
Called only once, immediately after setup()
. The SleepNow()
function is called to perform the first 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 message on waking.
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 loop with do..while()
syntax. Inside the loop is a _switch case
_ clause, which splits the process according to the state obtained by .state()
. State transitions call .next()
to rewrite internal variables in the state machine to the new state values.
step.b_more_loop()
is set to true if there is a state transition by .next()
. The purpose of this is to execute the code of the next state (case clause) without escaping loop()
when a state transition occurs.
Below is a description of each state.
STATE::INIT
sensor.dummy_work_ct_now = 0;
sensor.dummy_work_ct_max = random(10,1000);
step.next(STATE::WORK_JOB);
Initialises the sensor values of the dummies. One is determined randomly by an add counter and one by a counter stop value.
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, we work on a timer every 1ms; every Tick timer becomes TickTimer.available()
; every Tick timer adds a counter and when it reaches dummy_work_ct_max
, we move 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);
}
Call the Transmit()
function to request packet transmission. If the request succeeds, the function transits to STATE::WAIT_TXEVENT
and waits for the completion of transmission. Here, we use the timeout and flag functions of the SM_SIMPLE state machine to wait for completion (it is simple to determine the value of a variable by changing its value in the waiting loop).
We don't usually expect a single request to fail, but if it does, it will go to the STATE::EXIT_FATAL
exception handling state.
You should not sleep at this point, as the packet has not yet been sent. In most cases, you should wait for transmission to complete before continuing.
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 completion of transmission is judged by setting the flag of the state machine function with on_tx_comp()
described later. The timeout is judged by the elapsed time since .set_timeout()
was done by calling .is_timeout()
.
Normally you will get a completion notice whether the transmission succeeds or fails, but it will time out and go to STATE::EXIT_FATAL
for exception handling.
STATE::EXIT_NORMAL
SleepNow();
Call SleepNow()
to start the sleep process.
STATE::EXIT_FATAL
Serial << crlf << "!FATAL: RESET THE SYSTEM.";
delay(1000); // wait a while.
the_twelite.reset_system();
As a critical error, a system reset is performed.
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);
}
Periodic sleep is performed. The sleep time is set to a constant time blur using the random()
function. This is because when the transmission cycles of several devices are synchronized, the failure rate may increase significantly.
Before sleeping, the state of the SM_SIMPLE state machine is set 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);
}
This function requests to send a wireless packet to the parent device with ID=0x00
. The data to be stored is a four-character identifier (FOURCC
) commonly used in the Act sample, plus the system time [ms] and a dummy sensor value (sensor.dummy_work_ct_now
).
The first step is to get an object that contains the transmitted packets. This object can then be manipulated to set the data and conditions for transmission.
if (auto&& pkt = the_twelite.network.use<NWK_SIMPLE>().prepare_tx_packet()) {
In the mwx library, the if
statement is used to get an object and the bool
decision of the object is true
. Here, a board object is retrieved by the_twelite.network.use<NWK_SIMPLE>()
, and a packet object is retrieved by .prepare_tx_packet()
of the board object. Failure to retrieve the packet object is not normally expected, but when it does occur, it is when the transmit queue is full and the transmit request cannot be accepted. Since this example is for a single transmission only, the error is limited to an unexpected serious problem.
pkt << tx_addr(0x00) // Destination
<< tx_retry(0x1) // Number of resends
<< tx_packet_delay(0,0,2); // Transmission delay
For the resulting pkt
object, set the conditions for transmission (destination, retransmission, etc.) using the <<
operator. tx_addr
specifies the destination of the packet. The tx_addr
specifies the destination of the packet, the tx_retry
specifies the number of retransmissions, and the tx_packet_delay
specifies the 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 payload of a packet is an array of smblbuf<uint8_t>
derivatives obtained by pkt.get_payload()
. You can set the value of this array directly, but here we use pack_bytes()
to set the value.
This function can be specified by a variable number of arguments. The first parameter is an array object obtained from .get_payload()
.
make_pair(FOURCC,4)
: make_pair is from the C++ standard library and creates a std::pair object. It means to write out 4 bytes from the beginning for string type.(This is done to explicitly specify the number of bytes to be written, as the topic of including or excluding the end of an array of string types is confusing)If
uint32_t
type data is specified, 4 bytes of data are written in big-endian order.The same applies to data of type
uint16_t
.
Finally, .transmit()
is called to request sending. The return value is of the type MWX_APIRET
. After the request, the actual transmission takes place, which may take several to several tens of milliseconds to complete, depending on the transmission parameters and the size of the transmission. On completion, on_tx_comp()
is called.
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 is a system event that is called when the transmission is complete. Here, it is set to complete by .set_flag()
.
最終更新