環境センサーパル AMBIENT SENSE PAL を用い、センサー値の取得を行います。
子機はステートマシンによる状態遷移 により記述しています。
このサンプルはビヘイビア の記述方法のサンプルです。ビヘイビアはより複雑なアプリケーションを記述する際に用います。
アクトの機能
環境センサーパル AMBIENT SENSE PAL を用い、センサー値の取得を行います。
コイン電池で動作させるための、スリープ機能を利用します。
アクトの使い方
TWELITEの準備
親機にPALを使用する場合は、コイン電池での動作はできません。目安として50mA以上の電流を安定して得られるような電源環境を用意してください。
ファイル構成
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 () & 0x 07 );
// 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>
を登録します。
親機がMONOSTICKの場合は、PAL用のDIP SWの読み値は0となり、親機としてふるまいます。ただしこの動作はMONOSTICKの仕様として定義されているものではありません。
親機のビヘイビア
親機はスリープをしない受信機としてふるまい、子機からのパケットを受信したときにシリアルポートにパケットの情報を出力します。
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 )
<< " % lu mi=" << 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 , 0 UL );
}
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
の状態ハンドラが続けて呼び出されます。この時のイベントev
はE_EVENT_NEW_STATE
です。
ここではSHTC3, LTR308ALSの2センサーの動作開始を行います。一定時間経過すれば、センサーはデータ取得可能な状態になります。この時間待ちを66
ms設定のスリープで行います。スリープ前に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()
を実行してください。