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
After starting up, the system goes through an initialization process and then goes to sleep.
setup()
Initialise
begin()
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 initialization
loop()/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 send
loop()/WAIT_TX state
: Waiting for transmission completion
loop()/EXIT_NORMAL state
: Run sleep (back to 1.)
loop()/EXIT_FATAL state
:Resetting the module if an error occurs
To send packets, <NWK_SIMPLE>
is included. Also, basic definitions such as application ID are in "Common.h"
.
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.
Declares an SM_SIMPLE state machine (state transition) using the STATE
state enumeration.
The step
declared here contains functions for managing state, timeouts and waiting for processing.
In this sample we do not process the sensor data, but we prepare dummy data for explanation.
Initializes variables and class objects.
Initialization of the step
state machine
Initialization of the_twelite
class object
Register and initialize the network <NWK_SIMPLE>
(register DEVICE_ID
).
This is followed by the initiation of class objects and hardware.
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.
Called only once, immediately after setup()
. The SleepNow()
function is called to perform the first sleep procedure.
Called immediately after waking up. Here, it initializes the sensor data area and outputs a message on waking.
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.
Initialises the sensor values of the dummies. One is determined randomly by an add counter and one by a counter stop value.
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
.
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.
The Transmit()
function returns a MWX_APIRET
object, which holds a bool type success or failure and a value of up to 31 bits. It can be evaluated as a bool
type, so an if
statement will return true
if the send request succeeds, or false
if it fails.
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.
Call SleepNow()
to start the sleep process.
As a critical error, a system reset is performed.
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()
.
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.
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.
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.
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.
The maximum length of the payload is 91 bytes in the above example, see NWK_SIMPLE packet structure and maximum length for more information.
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
.
It is also possible to write data using a pointer of type uint8_t
.
The array object obtained from .get_payload()
is an array of size 0 with no data stored in it, but it is expanded by writing data to it (actually, writing data to an internal fixed-length buffer and updating the data size in internal management). The final size is the data size of the payload.
Here we use .begin()
to get a pointer to uint8_t*
, write data using this pointer, and set the last written size with .redim()
.
functions (macros) such as S_OCTET()
, S_WORD()
, and S_DWORD()
are used to write data, for example, S_OCTET(p, 'H')
is the same as *p = 'H'; p++;
.
The last .redim()
is a procedure to change the size of an array without initializing the buffer. Calling .resize()
clears everything to zero.
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.
``MWX_APIRET
is a wrapped class of uint32_t
type, which uses MSB as a failure success flag and 31 bits as data. It is a return type of pkt.transmit()
, and success or failure of the request (cast to bool
type) and the ID are stored in the data part (.get_value()
).
This is a system event that is called when the transmission is complete. Here, it is set to complete by .set_flag()
.