前回、TWELITE用のサンプルプログラムをダウンロードしてそのまま動かすことをやりました、また、子機用のプログラムを見てだいたいの概略を書きました。
alasixosaka.hatenablog.com
今回はその続きで、親機用のプログラムを見てみます。
親機用のプログラムは、Parent-MONOSTICKという名前のフォルダにあります。プログラムファイルはParent-MONOSTICK.cppです。中身を見ていきましょう。
冒頭の部分は子機とあまり変わりません。まずライブラリのインクルード。
// use twelite mwx c++ template library #include <TWELITE> #include <NWK_SIMPLE> #include <MONOSTICK> #include <STG_STD>
子機ではセンサー用のライブラリSNS_BME280とSNS_SHT3Xを読み込んでいましたが、親機はMONOSTICKを読み込んでいます。MONOSTICKを使わない場合は必要ないような気がします。LED制御とウォッチドックタイマー制御を含むとマニュアルに書いてあります。
続いてデバイスの設定です。アプリケーションIDを子機と同じく1234abcdとしています。チャンネルも13です。オプションビット用の変数を0にしています。インタラクティブモードでオプションビットの設定を使用しないということのようです。そして、その次が関数プロトライプの宣言です(実はあまりよくわかっていないのですが、この関数で送られてきたパケットを解析して適切なデータを取り出しています)。
/*** Config part */ // application ID const uint32_t DEFAULT_APP_ID = 0x1234abcd; // channel const uint8_t DEFAULT_CHANNEL = 13; // option bits uint32_t OPT_BITS = 0; /*** function prototype */ bool analyze_payload(packet_rx& rx);
続いてセットアップに入ります。
void setup() { /*** SETUP section */ auto&& brd = the_twelite.board.use<MONOSTICK>(); auto&& set = the_twelite.settings.use<STG_STD>(); auto&& nwk = the_twelite.network.use<NWK_SIMPLE>();
まず、モノスティック用の関数群(ビヘイビアというようです)の読み込み、インタラクティブモード関連のビヘイビアの読み込み、通信用ビヘイビアの読み込みです。通信はこちらも子機同様シンプルモードを使います。
次がインタラクティブモードの設定です。インタラクティブモードではアプリケーションの名前、アプリケーションのID、チャンネル、LID(論理ID)を表示します。そして、EEPROMから値をロードします。
// settings: configure items set << SETTINGS::appname("PARENT"); set << SETTINGS::appid_default(DEFAULT_APP_ID); // set default appID set << SETTINGS::ch_default(DEFAULT_CHANNEL); // set default channel set << SETTINGS::lid_default(0x00); // set default LID set.hide_items(E_STGSTD_SETID::OPT_DWORD2, E_STGSTD_SETID::OPT_DWORD3, E_STGSTD_SETID::OPT_DWORD4, E_STGSTD_SETID::ENC_KEY_STRING, E_STGSTD_SETID::ENC_MODE); set.reload(); // load from EEPROM. OPT_BITS = set.u32opt1(); // this value is not used in this example.
先ほどの設定値が入った変数setをthe_tweliteに設定し、受信を設定します。
// the twelite main class the_twelite << set // apply settings (appid, ch, power) << TWENET::rx_when_idle() // open receive circuit (if not set, it can't listen packts from others) ;
また、ネットワークの設定も、先ほどのsetで設定した後、論理IDだけは(念のため)0に設定しています。これは、インタラクティブモードで間違って別のIDを設定してしまうと親機として動作できなくなるためと思われます。
// Register Network nwk << set; // apply settings (LID and retry) nwk << NWK_SIMPLE::logical_id(0x00) // set Logical ID. (0x00 means parent device) ;
次は、LEDの点滅間隔の設定ですが、説明は省略します。
そして、TWELITEをスタートさせます。
/*** BEGIN section */ the_twelite.begin(); // start twelite!
続いてループ関数ですが、中身が空です。親機は無線を受信したときの割り込み処理ですべてを処理してしまっているようです。
つまり、常にループで回って割り込み待ちをしておき、割り込みがあった時だけその処理をしているということになります。
その割り込み処理ルーチンですが、
void on_rx_packet(packet_rx& rx, bool_t &handled) { Serial << ".. coming packet (" << int(millis()&0xffff) << ')' << mwx::crlf;
から始まります。2行目はシリアルにパケットの着信を書き込んでいるだけです。
次に、
if(0) { // this part is disabed. serparser_attach pout; pout.begin(PARSER::ASCII, rx.get_psRxDataApp()->auData, rx.get_psRxDataApp()->u8Len, rx.get_psRxDataApp()->u8Len); Serial << "RAW PACKET -> "; pout >> Serial; Serial.flush(); }
という部分がありますが、If文のかっこの中が0なので、このルーチンはコメントにある通り通ることがないようになっています。デバック用の設定のようで、子機から送られてきた生のデータを見る時にかっこの中を1に書き換えると"RAW PCKET"に引き続き表示されるようになります。
その次はif文のかっこ内が1なので、必ず実行されます。ここも理解があいまいですが、bufという変数に、送信元ID、固定のCC、応答ID、送信元のシリアルID、送信先のシリアルID、LQI(通信品質)、データ長、データの順に格納され、それをシリアル出力しているようです。これは、RAW PACKETとあまり変わらないものになります。データの中身は、前回の子機のプログラムの説明を見てください。
if (1) { smplbuf_u8<256> buf; pack_bytes(buf , uint8_t(rx.get_addr_src_lid()) // *1:src addr (LID) , uint8_t(0xCC) // *2:cmd id (0xCC, fixed) , uint8_t(rx.get_psRxDataApp()->u8Seq) // *3:seqence number , uint32_t(rx.get_addr_src_long()) // *4:src addr (long) , uint32_t(rx.get_addr_dst()) // *5:dst addr , uint8_t(rx.get_lqi()) // *6:LQI , uint16_t(rx.get_length()) // *7:payload length , rx.get_payload() // *8:payload // , make_pair(rx.get_payload().begin() + 4, rx.get_payload().size() - 4) // note: if you want the part of payload, use make_pair(). ); serparser_attach pout; pout.begin(PARSER::ASCII, buf.begin(), buf.size(), buf.size()); Serial << "ASCII FMT -> "; pout >> Serial; Serial.flush(); }
引き続きパケットの中身を解析する関数を呼び出します。
// packet analyze analyze_payload(rx);
その関数の中身ですが、
まず冒頭の部分でよくわからない記述があります。#if 1 .... #else ... #endifとあるので、if 1以下の部分しか実行されないように思います。いずれにしても、やっていることは、アプリ固有の4文字の取得です。BMP280とSHT3x用のアプリの場合はこの4文字は"SBS1"でした。実はこれが問題なのです。どう問題なのかは後で書きます。
#if 1 // expand packet payload (shall match with sent packet data structure, see pack_bytes()) uint8_t fourchars[4]{}; // init all elements as default (0). auto&& np = expand_bytes(rx.get_payload().begin(), rx.get_payload().end() , fourchars ); #else // an example to pass std::pair<char*,int>. char fourchars[5]{}; auto&& np = expand_bytes( rx.get_payload().begin(), rx.get_payload().end() , make_pair((char *)fourchars, 4) ); #endif
次は、4文字が正しく読めなかった場合です。
// if heading 4 bytes are not present, unexpected packet data. if (np == nullptr) return false;
それからそれぞれのアプリに対応した処理が続きます。例えば、
// Slp_Wk_and_Tx if (!b_handled && !strncmp((char*)fourchars, "TXSP", 4)) {
以下の部分は、Slp_Wk_and_Txというアプリに対応した部分で4文字が"TXSP"です。以下、"BAT1"、"PAB1"、"PAB2"、"PMG1"、"PMT1"と続きますが、SBS1はありません。どうも、エクストラのアプリになっていたので、標準では対応していないようです。従って、本当は、測定した温度、湿度を表示してほしいところですが、"..not analyzed.." と表示されてしまいます。
そこで、SBS1に対応するように以下の文をこの関数の中に追記しました。これが、前回ちらっと書いた、Parent-MONOSTICK_2というアプリです。
if (!b_handled && ( !strncmp((char*)fourchars, "SBS1", 4) // BMx280 ) ) { b_handled = true; //uint32_t u32lumi; uint16_t u16temp; //uint16_t u16humd; np = expand_bytes(np, rx.get_payload().end() //, u32lumi , u16temp //, u16humd ); if (np != nullptr) { Serial //<< format("Lumi:%d", u32lumi) << " Temp:" << div100(int16_t(u16temp)) //<< " Humid:" << div100(int16_t(u16humd)) ; } else { Serial << ".. error .."; } }
実は、これは4文字コードでは"PAB1"というアプリの処理そのままなのですが、測定されない、照度と湿度の項目はコメントアウトしています。もちろん、子機側からの送信データも合わせないと正しく表示されないので、子機側から送るデータも余分な部分はカットして、BMP280の温度のデータのみにしてあります。温度のデータは16ビットの符号なし変数u16tempに格納されます。それを符号付きの16ビット変数に変換しさらに100で割ってシリアルから表示します。この処理で合っているのか自信はないのですが、一応室温らしきデータは表示されました。つまり、送られてくるデータは2バイトのデータでそれを一旦符号なしの16ビット変数に格納します。なぜ、いきなり符号ありにしないのかよくわからないのですが、それをさらに符号ありの16ビット変数に変換しています。これでマイナスが表示されるようになるのでしょう。ちなみに湿度も同じ処理をしていますが、湿度がマイナスになることはないはずなのでなぜこのような処理なのかよくわかりません。データは実際の温度を100倍したものなので、100で割って小数点2桁の実数に変換しています。
子機側のプログラムの変更についてはまた、次回に書いてみたいと思います。