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

Sample using the environmental sensor PAL
    Using the Environmental Sensor PAL AMBIENT SENSE PAL to acquire sensor values.

    Act Functions

    • Uses the environmental sensor PAL AMBIENT SENSE PAL to acquire sensor values.
    • Utilizes sleep functions to operate with coin batteries.

    How to Use the Act

    Preparing TWELITE

    RoleExample
    ParentMONOSTICK BLUE or RED
    ChildBLUE PAL or RED PAL + Environmental Sensor PAL AMBIENT SENSE PAL

    File Structure

    • PAL_AMB-behavior.hpp : Defines only setup(). Reads DIP switches, and if D1..D3 are in the up position, it operates as a parent device; otherwise, it sets the ID corresponding to the DIP switch as a child device.
    • Parent/myAppBhvParent.hpp : Behavior class definition for the parent device
    • Parent/myAppBhvParent.cpp : Implementation
    • Parent/myAppBhvParent-handlers.cpp : Handler implementation
    • Parent/myAppBhvParent.hpp : Behavior class definition for the child device
    • Parent/myAppBhvParent.cpp : Implementation
    • Parent/myAppBhvParent-handlers.cpp : Handler implementation

    The behavior name for the parent device is <MY_APP_PARENT>, and for the child device is <MY_APP_CHILD>.

    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 read value is 0, the parent behavior <MY_APP_PARENT> is registered; otherwise, the child behavior <MY_APP_CHILD> is registered.

    Parent Behavior

    The parent device behaves as a receiver that does not sleep and outputs packet information to the serial port when 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 device 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 parent device’s interrupt handler 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 is described as a reference for state transitions and does not have a significant meaning for the application operation. It executes state transitions triggered by the E_ORDER_KICK event from the button and timeouts.

    Child Behavior

    The operation flow of the child device is the same as PAL_AMB-usenap. It repeats “wake up → start sensor operation → short sleep → wake up → acquire sensor values → wireless transmission → wait for transmission completion → sleep” from the initial 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.

    (You may write this process directly in on_begin() without using _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
    }

    Describes the wake-up process from sleep.

    Here, the initial Wire.begin() is executed. It is redundant to include this on subsequent wake-ups from sleep. This process can 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
    }

    Processes the E_ORDER_KICK message to the state machine when transmission is complete.

    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;

    Defines state names.

    MY_APP_CHILD::shtc3_???()

    MWX_APIRET MY_APP_CHILD::shtc3_start()
    MWX_APIRET MY_APP_CHILD::shtc3_read()

    Example implementation for sensor acquisition of SHTC3. For details such as 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)

    Example implementation for sensor acquisition of LTR308ALS. For details such as 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 waking from sleep.

    It is called when the startup cold boot check PEV_is_coldboot(ev,evarg) returns true. Since on_begin() immediately goes to sleep, no state transitions are included here. At this point, major initialization is not yet complete, so complex processes such as wireless packet transmission cannot be performed. To perform such processes, an event is sent from on_begin() to trigger the first state transition.

    After waking 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 transitioning from STATE_IDLE after waking from sleep, the STATE_SENSOR handler is called with the event E_EVENT_NEW_STATE.

    Here, the operation of two sensors, SHTC3 and LTR308ALS, is started. After a certain time, the sensors become ready for data acquisition. This wait time is performed by sleeping for 66 ms. Note that PEV_KeepStateOnWakeup() is called before sleeping. This call keeps the state as STATE_SENSOR after waking up, instead of returning to STATE_IDLE.

    After waking from this short sleep, the first call with PEV_is_warmboot(ev,evarg) returns true. At this point, wireless packet transmission can be performed. It 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, on the E_EVENT_NEW_STATE event, sensor data is read and the wireless packet transmission procedure begins. For transmission procedure details, please 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
    }
    
        // ↓ ↓ ↓ Message sending
    
    } else if (ev == E_ORDER_KICK && evarg == uint32_t(u8txid)) {
    		Serial << "[STATE_TX] SUCCESS TX(" << int(evarg) << ')' << mwx::crlf;
    		PEV_SetState(STATE_SLEEP);
    }

    Waiting for transmission completion is handled differently from loop-based act descriptions. It waits for a message from transmit_complete() via PEV_Process() to confirm completion. When the message is received, it goes to sleep. The sleep process is done 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, timeout processing is performed. This assumes the case where the transmission completion message does not return. PEV_u32Elaspsed_ms() returns the elapsed time in ms since transitioning to this state. If time passes, the system resets (the_twelite.reset_system()), assuming this timeout is a critical error.

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

    Performs sleep. This is described inside the E_EVENT_NEW_STATE event immediately after transitioning from the previous state. Since other events might be called just before sleeping, always execute the_twelite.sleep() inside a condition that runs only once.