セクションの複数ページをまとめています。 印刷またはPDF形式で保存...

もとのページに戻る

2024-11-14 現在

PAL_AMB-behavior

環境センサパルを使用したビヘイビアの例

    環境センサーパル AMBIENT SENSE PAL を用い、センサー値の取得を行います。

    • ビヘイビアを用いた親機子機の記述を行っています。
    • センサーを値を得るのにボードビヘイビアの機能を使わずWireを用いて直接記述しています。
    • 子機はステートマシンによる状態遷移により記述しています。

    アクトの機能

    • 環境センサーパル AMBIENT SENSE PAL を用い、センサー値の取得を行います。
    • コイン電池で動作させるための、スリープ機能を利用します。

    アクトの使い方

    TWELITEの準備

    役割
    親機MONOSTICK BLUEまたはRED
    子機BLUE PAL または RED PAL +環境センサーパル AMBIENT SENSE PAL

    ファイル構成

    • PAL_AMB-behavior.hpp : setup()のみの定義です。DIP-SWを読み出し、D1..D3が上位置の場合は親機として動作し、それ以外は子機としてDIP SWに対応するIDをセットします。
    • Parent/myAppBhvParent.hpp : 親機用のビヘイビアクラス定義
    • Parent/myAppBhvParent.cpp : 実装
    • Parent/myAppBhvParent-handlers.cpp : ハンドラーの実装
    • Parent/myAppBhvParent.hpp : 子機用のビヘイビアクラス定義
    • Parent/myAppBhvParent.cpp : 実装
    • Parent/myAppBhvParent-handlers.cpp : ハンドラーの実装

    親機のビヘイビア名は<MY_APP_PARENT>、子機は<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>();
    }

    DIP SWの読み値が0の場合は親機用のビヘイビア<MY_APP_PARENT>を、それ以外の場合は子機用のビヘイビア<MY_APP_CHILD>を登録します。

    親機のビヘイビア

    親機はスリープをしない受信機としてふるまい、子機からのパケットを受信したときにシリアルポートにパケットの情報を出力します。

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

    親機用がパケットを受信したときは、パケットの先頭4文字が照合(FOURCHARS)できれば、そのパケット内容を表示します。

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

    親機の割り込みハンドラは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;
    }

    PAL上のボタン(5)が押されたときには、状態マシンに対してE_ORDER_KICKイベントを発行します。

    MY_APP_PARENT::MWX_STATE(E_MWX::STATE_0 .. 3)

    状態マシンは、状態遷移の参考として記述したもので、アプリケーションの動作上意味のあるものではありません。ボタンから送付されるE_ORDER_KICKイベントによる状態遷移や、タイムアウトなどを実行しています。

    子機のビヘイビア

    子機の動作の流れはPAL_AMB-usenapと同じです。初回スリープから「起床→センサー動作開始→短いスリープ→起床→センサー値取得→無線送信→無線送信完了待ち→スリープ」を繰り返します。

    MY_APP_CHILD::on_begin()

    void _begin() {
        // sleep immediately.
        Serial << "..go into first sleep (1000ms)" << mwx::flush;
        the_twelite.sleep(1000);
    }

    on_begin()から呼び出される_begin()関数では、初回スリープを実行しています。

    (※_begin()関数で本処理を記述せずon_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
    }

    スリープからの起床処理を記述しています。

    ここで初回のWire.begin()を実行しています。2回目以降のスリープ起床時では冗長な記述です。この処理は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
    }

    送信完了時に状態マシンに対してE_ORDER_KICKメッセージを処理します。

    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;

    状態名を定義しています。

    MY_APP_CHILD::shtc3_???()

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

    SHTC3用のセンサー取得実装例です。送付コマンド等の詳細はSHTC3のデータシートなどを参考にしてください。

    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)

    LTR308ALSのセンサー取得実装例です。送付コマンド等の詳細はLTR308ALSのデータシートなどを参考にしてください。

    WireWriteAndGet()addrのデバイスに対してcmdを1バイト送信してから、1バイト受信して値を返します。

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

    0番の状態は特別な意味を持ちます。起動直後またはスリープ復帰後の状態です。

    起動直後PEV_is_coldboot(ev,evarg)判定がtrueになって呼び出されます。on_begin()から、そのままスリープしてしまうため、状態遷移するようなコードも含まれません。**この時点では主要な初期化がまだ終わっていませんので、無線パケットの送信など複雑な処理を行うことが出来ません。**そのような処理を行うための最初の状態遷移を行うためにはon_begin()からイベントを送り、そのイベントに従って状態遷移を行います。

    スリープ復帰後はPEV_is_warmboot(ev,evarg)trueになる呼び出しが最初にあります。PEV_SetState()を呼び出し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);
    	}
    }

    スリープ復帰後STATE_IDLEから遷移したとき、STATE_SENSORの状態ハンドラが続けて呼び出されます。この時のイベントevE_EVENT_NEW_STATEです。

    ここではSHTC3, LTR308ALSの2センサーの動作開始を行います。一定時間経過すれば、センサーはデータ取得可能な状態になります。この時間待ちを66ms設定のスリープで行います。スリープ前にPEV_KeepStateOnWakeup()が呼ばれている点に注意してください。この呼び出しを行うと、スリープ復帰後の状態はSTATE_IDLEではなく、スリープしたときの状態、つまりSTATE_SENSORとなります。

    短いスリープから復帰するとPEV_is_warmboot(ev,evarg)判定がtrueとなる呼び出しが最初に発生します。この呼び出し時点で、無線パケットの送信などを行うことが出来ます。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()) {

    ここではE_EVENT_NEW_STATEイベントの時に、センサーデータ読み出し、無線パケットの送信手続きに入ります。送信手続きの詳細は他のアクトサンプル例を参考にしてください。

    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
    }
    
        // ↓ ↓ ↓ メッセージ送付
    
    } else if (ev == E_ORDER_KICK && evarg == uint32_t(u8txid)) {
    		Serial << "[STATE_TX] SUCCESS TX(" << int(evarg) << ')' << mwx::crlf;
    		PEV_SetState(STATE_SLEEP);
    }

    送信完了まちの処理はループでのアクト記述と違い、transmit_complete()からのPEV_Process()によるメッセージを待つことで完了確認としています。メッセージを受け取った時点でスリープします。スリープ処理は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();
    	}

    最後にタイムアウト処理を行っています。万が一送信パケットの完了メッセージが戻ってこなかった場合を想定します。PEV_u32Elaspsed_ms()はその状態に遷移してからの経過時間を[ms]で返します。時間経過した場合は、上記では(このタイムアウトは余程のことだとして)システムリセット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
    	}
    }

    スリープを行います。前の状態から遷移した直後のE_EVENT_NEW_STATEに記述します。スリープ直前に他のイベントが呼ばれる可能性がありますので、必ず1回だけ実行される判定式の中でthe_twelite.sleep()を実行してください。