This is the multi-page printable view of this section. Click here to print...

Return to the regular view of this page

As of 2025-07-24

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.

    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

    RoleExample
    ParentMONOSTICK BLUE or RED
    ChildBLUE PAL or RED PAL + Ambient Sensor PAL

    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.

    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.