For suitable output, we recommend to use Google Chrome (15+) or Microsoft Edge (79+).
As of 2025-07-24Behavior
An advanced framework for implementing complex applications
In Behavior, defining a class in a specified manner allows it to be registered with the
the_twelite
class object. Once registered, the behavior is integrated into TWENET and operates accordingly. User code can describe the application’s behavior via this mechanism. Unlike loop-based implementations, it enables defining interrupt handlers and callback functions from TWENET. Although it requires more code, this approach is suitable for constructing more complex applications.
Class Definition (.hpp
)
A behavior is defined using a class structure like the one below.
class MY_APP_CLASS: MWX_APPDEFS_CRTP(MY_APP_CLASS)
{
public:
static const uint8_t TYPE_ID = 0x01;
// load common definition for handlers
#define __MWX_APP_CLASS_NAME MY_APP_CLASS
#include "_mwx_cbs_hpphead.hpp"
#undef __MWX_APP_CLASS_NAME
public:
// constructor
MY_APP_CLASS() {}
void _setup() {}
void _begin() {}
public:
// TWENET callback handler (mandate)
void loop() {}
void on_sleep(uint32_t & val) {}
void warmboot(uint32_t & val) {}
void wakeup(uint32_t & val) {}
void on_create(uint32_t& val) { _setup(); }
void on_begin(uint32_t& val) { _begin(); }
void on_message(uint32_t& val) { }
public:
void network_event(mwx::packet_ev_nwk& pEvNwk) {}
void receive(mwx::packet_rx& rx) {}
void transmit_complete(mwx::packet_ev_tx& evTx) {}
};
In the example above, a behavior class named MY_APP_CLASS
is defined. Several places require the name MY_APP_CLASS
.
class MY_APP_CLASS: MWX_APPDEFS_CRTP(MY_APP_CLASS)
Defines the class name and the base (parent) class. MWX_APPDEFS_CRTP()
is a macro.
#define __MWX_APP_CLASS_NAME MY_APP_CLASS
#include "_mwx_cbs_hpphead.hpp"
#undef __MWX_APP_CLASS_NAME
Includes the necessary definitions via #include
.
Defines the constructor.
Methods
loop()
The main loop function, serving the same role as the globally defined loop()
.
on_create()
on_create()
is called when the object is created (via the use<>()
method). The val
parameter is reserved for future extensions.
on_begin()
on_begin()
is called after setup()
completes. The val
parameter is reserved for future extensions.
on_sleep()
Called before entering sleep. The val
parameter is reserved for future extensions.
warmboot()
Called at the initial stage of wakeup from sleep. The val
parameter is reserved for future extensions.
At this point, peripherals are not yet initialized. You can check the cause of the sleep wakeup.
wakeup()
Called upon waking from sleep. The val
parameter is reserved for future extensions.
It is also possible to invoke sleep here.
receive()
void receive(mwx::packet_rx& rx)
Called when a packet is received, with the received packet information passed as rx
.
transmit_complete()
void transmit_complete(mwx::packet_ev_tx& evTx)
Called when packet transmission is complete, with the transmission information passed as evTx
. evTx.u8CbId
is the ID used during transmission, and evTx.bStatus
is a flag indicating transmission success (1
) or failure (0
).
1 - PAL_AMB-behavior
A sample behavior using the Ambient Sensor PAL
This sample retrieves sensor values using the Ambient Sensor PAL.
- Demonstrates parent/child configuration using Behavior.
- Instead of using the Board Behavior functionality, sensor values are read directly using
Wire
. - The child device is implemented as a state machine.
This is a sample showing how to implement an Act using
Behavior. Behavior is used for describing more complex applications.
Act Functionality
- Retrieves sensor values using the Ambient Sensor PAL.
- Uses sleep functionality to enable operation with coin cell batteries.
How to Use the Act
Preparing TWELITE
If using PAL as the parent device, coin cell operation is not supported. Make sure the power supply can stably provide at least 50mA as a guideline.
File Structure
- PAL_AMB-behavior.hpp: Defines only
setup()
. Reads DIP switches and acts as a parent if D1..D3 are ON; otherwise, acts as a child using the DIP switch as ID. - Parent/myAppBhvParent.hpp: Behavior class definition for the parent.
- Parent/myAppBhvParent.cpp: Implementation.
- Parent/myAppBhvParent-handlers.cpp: Handler implementations.
- Child/myAppBhvChild.hpp: Behavior class definition for the child.
- Child/myAppBhvChild.cpp: Implementation.
- Child/myAppBhvChild-handlers.cpp: Handler implementations.
The behavior name for the parent is <MY_APP_PARENT>
, and for the child it is <MY_APP_CHILD>
.
Initialization setup()
// now read DIP sw status can be read.
u8ID = (brd.get_DIPSW_BM() & 0x07);
// Register App Behavior (set differnt Application by DIP SW settings)
if (u8ID == 0) {
// put settings to the twelite main object.
the_twelite
<< TWENET::appid(APP_ID) // set application ID (identify network group)
<< TWENET::channel(CHANNEL) // set channel (pysical channel)
<< TWENET::rx_when_idle(); // open RX channel
the_twelite.app.use<MY_APP_PARENT>();
} else {
// put settings to the twelite main object.
the_twelite
<< TWENET::appid(APP_ID) // set application ID (identify network group)
<< TWENET::channel(CHANNEL); // set channel (pysical channel)
the_twelite.app.use<MY_APP_CHILD>();
}
If the DIP switch value is 0, the parent behavior <MY_APP_PARENT>
is registered; otherwise, the child behavior <MY_APP_CHILD>
is used.
If using MONOSTICK as the parent device, the DIP switch value for PAL will be 0, causing it to behave as a parent. However, this behavior is not officially defined by the MONOSTICK specification.
Parent Behavior
The parent device acts as a receiver that does not sleep and outputs packet information to the serial port upon receiving packets from child devices.
MY_APP_PARENT::receive()
void MY_APP_PARENT::receive(mwx::packet_rx& rx) {
uint8_t msg[4];
uint32_t lumi;
uint16_t u16temp, u16humid;
// expand packet payload (shall match with sent packet data structure, see pack_bytes())
auto&& np = expand_bytes(rx.get_payload().begin(), rx.get_payload().end(), msg);
// if PING packet, respond pong!
if (!strncmp((const char*)msg, (const char*)FOURCHARS, 4)) {
// get rest of data
expand_bytes(np, rx.get_payload().end(), lumi, u16temp, u16humid);
// print them
Serial << format("Packet(%x:%d/lq=%d/sq=%d): ",
rx.get_addr_src_long(), rx.get_addr_src_lid(),
rx.get_lqi(), rx.get_psRxDataApp()->u8Seq)
<< "temp=" << double(int16_t(u16temp)/100.0)
<< "C humid=" << double(int16_t(u16humid)/100.0)
<< "% lumi=" << int(lumi)
<< mwx::crlf << mwx::flush;
}
}
When the parent receives a packet, if the first four characters of the packet match (FOURCHARS
), the packet content is displayed.
MY_APP_PARENT::MWX_TICKTIMER_INT()
MWX_TICKTIMER_INT(uint32_t arg, uint8_t& handled) {
// blink LED
digitalWrite(PAL_AMB::PIN_LED,
((millis() >> 9) & 1) ? PIN_STATE::HIGH : PIN_STATE::LOW);
}
The interrupt handler for the parent blinks the LED.
MY_APP_PARENT::MWX_DIO_EVENT(PAL_AMB::PIN_BTN)
MWX_DIO_EVENT(PAL_AMB::PIN_BTN, uint32_t arg) {
Serial << "Button Pressed" << mwx::crlf;
static uint32_t u32tick_last;
uint32_t tick = millis();
if (tick - u32tick_last > 100) {
PEV_Process(E_ORDER_KICK, 0UL);
}
u32tick_last = tick;
}
When the button (5) on the PAL is pressed, an E_ORDER_KICK
event is issued to the state machine.
MY_APP_PARENT::MWX_STATE(E_MWX::STATE_0 .. 3)
The state machine described here serves as a reference for state transitions and does not have functional significance in the application. It handles state transitions triggered by the E_ORDER_KICK
event from the button and timeouts.
Child Behavior
The operation flow of the child device is similar to PAL_AMB-usenap
: it repeatedly performs the cycle of wake up → start sensor operation → short sleep → wake up → read sensor values → send via radio → wait for transmission completion → sleep.
MY_APP_CHILD::on_begin()
void _begin() {
// sleep immediately.
Serial << "..go into first sleep (1000ms)" << mwx::flush;
the_twelite.sleep(1000);
}
The _begin()
function called from on_begin()
performs the initial sleep.
(Note: It is also acceptable to write this process directly in on_begin()
instead of _begin()
.)
MY_APP_CHILD::wakeup()
void wakeup(uint32_t & val) {
Serial << mwx::crlf << "..wakeup" << mwx::crlf;
// init wire device.
Wire.begin();
// turn on LED
digitalWrite(PAL_AMB::PIN_LED, PIN_STATE::LOW);
// KICK it!
PEV_Process(E_ORDER_KICK, 0); // pass the event to state machine
}
This describes the wake-up process from sleep.
Here, the initial Wire.begin()
is executed. For subsequent wake-ups from sleep, this is redundant. This process may also be moved to on_begin()
.
MY_APP_CHILD::transmit_complete()
void transmit_complete(mwx::packet_ev_tx& txev) {
Serial << "..txcomp=" << int(txev.u8CbId) << mwx::crlf;
PEV_Process(E_ORDER_KICK, txev.u8CbId); // pass the event to state machine
}
Upon transmission completion, an E_ORDER_KICK
message is sent to the state machine.
MY_APP_CHILD::transmit_complete()
static const uint8_t STATE_IDLE = E_MWX::STATE_0;
static const uint8_t STATE_SENSOR = E_MWX::STATE_1;
static const uint8_t STATE_TX = E_MWX::STATE_2;
static const uint8_t STATE_SLEEP = E_MWX::STATE_3;
State names are defined here.
MY_APP_CHILD::shtc3_???()
MWX_APIRET MY_APP_CHILD::shtc3_start()
MWX_APIRET MY_APP_CHILD::shtc3_read()
These are sensor acquisition implementations for SHTC3. For details of commands sent, please refer to the SHTC3 datasheet.
MY_APP_CHILD::ltr308als_???()
MWX_APIRET MY_APP_CHILD::ltr308als_read()
MWX_APIRET MY_APP_CHILD::ltr308als_start()
static MWX_APIRET WireWriteAngGet(uint8_t addr, uint8_t cmd)
These are sensor acquisition implementations for LTR308ALS. For details of commands sent, please refer to the LTR308ALS datasheet.
WireWriteAndGet()
sends one byte cmd
to the device at addr
, then receives one byte and returns the value.
MY_APP_CHILD::STATE_IDLE (0)
MWX_STATE(MY_APP_CHILD::STATE_IDLE, uint32_t ev, uint32_t evarg) {
if (PEV_is_coldboot(ev,evarg)) {
Serial << "[STATE_IDLE:START_UP(" << int(evarg) << ")]" << mwx::crlf;
// then perform the first sleep at on_begin().
} else
if (PEV_is_warmboot(ev,evarg)) {
Serial << "[STATE_IDLE:START_UP(" << int(evarg) << ")]" << mwx::crlf;
PEV_SetState(STATE_SENSOR);
}
}
State 0 has a special meaning: it is the state immediately after startup or wake-up from sleep.
On cold boot, PEV_is_coldboot(ev,evarg)
returns true and this is called. Since on_begin()
immediately puts the device to sleep, no state transition code is included here. At this point, major initialization has not yet completed, so complex operations such as wireless packet transmission cannot be performed. To perform such operations, the first state transition is triggered by sending an event from on_begin()
and performing state transition accordingly.
On wake-up from sleep, PEV_is_warmboot(ev,evarg)
returns true on the first call. It calls PEV_SetState()
to transition to STATE_SENSOR
.
MY_APP_CHILD::STATE_SENSOR
MWX_STATE(MY_APP_CHILD::STATE_SENSOR, uint32_t ev, uint32_t evarg) {
if (ev == E_EVENT_NEW_STATE) {
Serial << "[STATE_SENSOR:NEW] Start Sensor." << mwx::crlf;
// start sensor capture
shtc3_start();
ltr308als_start();
// take a nap waiting finish of capture.
Serial << "..nap for 66ms" << mwx::crlf;
Serial.flush();
PEV_KeepStateOnWakeup(); // stay this state on waking up.
the_twelite.sleep(66, false, false, TWENET::SLEEP_WAKETIMER_SECONDARY);
} else
if (PEV_is_warmboot(ev,evarg)) {
// on wakeup, code starts here.
Serial << "[STATE_SENSOR:START_UP] Wakeup." << mwx::crlf;
PEV_SetState(STATE_TX);
}
}
When waking up from sleep and transitioning from STATE_IDLE
, the handler for STATE_SENSOR
is called with event E_EVENT_NEW_STATE
.
Here, sensor capture is started for SHTC3 and LTR308ALS. After a certain time, the sensors will be ready to provide data. This wait time is implemented as a sleep of 66 ms. Note that PEV_KeepStateOnWakeup()
is called before sleeping. This call ensures that upon wake-up, the state remains STATE_SENSOR
instead of returning to STATE_IDLE
.
After waking from the short sleep, PEV_is_warmboot(ev,evarg)
returns true on the first call, allowing wireless packet transmission and other operations. The state transitions to STATE_TX
.
MY_APP_CHILD::STATE_TX
MWX_STATE(MY_APP_CHILD::STATE_TX, uint32_t ev, uint32_t evarg)
static int u8txid;
if (ev == E_EVENT_NEW_STATE) {
Serial << "[STATE_TX:NEW]" << mwx::crlf;
u8txid = -1;
auto&& r1 = shtc3_read();
auto&& r2 = ltr308als_read();
Serial << "..shtc3 t=" << int(i16Temp) << ", h=" << int(i16Humd) << mwx::crlf;
Serial << "..ltr308als l=" << int(u32Lumi) << mwx::crlf;
if (r1 && r2) {
if (auto&& pkt = the_twelite.network.use<NWK_SIMPLE>().prepare_tx_packet()) {
Here, upon receiving the E_EVENT_NEW_STATE
event, sensor data is read and the wireless packet transmission procedure begins. For details on the transmission procedure, refer to other Act sample examples.
void transmit_complete(mwx::packet_ev_tx& txev) {
Serial << "..txcomp=" << int(txev.u8CbId) << mwx::crlf;
PEV_Process(E_ORDER_KICK, txev.u8CbId); // pass the event to state machine
}
// ↓ ↓ ↓ Send message
} else if (ev == E_ORDER_KICK && evarg == uint32_t(u8txid)) {
Serial << "[STATE_TX] SUCCESS TX(" << int(evarg) << ')' << mwx::crlf;
PEV_SetState(STATE_SLEEP);
}
The waiting for transmission completion is handled by waiting for the message sent from transmit_complete()
via PEV_Process()
. Upon receiving the message, the device goes to sleep by transitioning to STATE_SLEEP
.
if (PEV_u32Elaspsed_ms() > 100) {
// does not finish TX!
Serial << "[STATE_TX] FATAL, TX does not finish!" << mwx::crlf << mwx::flush;
the_twelite.reset_system();
}
Finally, a timeout process is implemented. This covers the case where the transmission completion message never arrives. PEV_u32Elaspsed_ms()
returns the elapsed time in milliseconds since entering this state. If the timeout occurs, the system resets with the_twelite.reset_system()
.
MY_APP_CHILD::STATE_SLEEP
MWX_STATE(MY_APP_CHILD::STATE_SLEEP, uint32_t ev, uint32_t evarg) {
if (ev == E_EVENT_NEW_STATE) {
Serial << "..sleep for 5000ms" << mwx::crlf;
pinMode(PAL_AMB::PIN_BTN, PIN_MODE::WAKE_FALLING_PULLUP);
digitalWrite(PAL_AMB::PIN_LED, PIN_STATE::HIGH);
Serial.flush();
the_twelite.sleep(5000); // regular sleep
}
}
The device goes to sleep. The sleep call is placed inside the conditional block that executes only once immediately after transitioning from the previous state (E_EVENT_NEW_STATE
). Since other events may be called before sleep, ensure that the_twelite.sleep()
is called exactly once here.