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

もとのページに戻る

2024-11-14 現在

BRD_APPTWELITE

デジタル・アナログ信号伝送
    App_Twelite と同じ配線を想定したボードサポート <BRD_APPTWELITE> を用いたサンプルです。

    アクトの機能

    • M1を読み取り、親機か子機かを決めます。
    • DI1-DI4 の値を読み取ります。Buttons クラスにより、チャタリングの影響を小さくするため、連続で同じ値になったときにはじめて変化が通知されます。変化があったときには通信を行います。
    • AI1-AI4 の値を読み取ります。
    • DIの変化または1秒おきに、DI1-4, AI1-4, VCC の値を、自身が親機の場合は子機へ、子機の場合は親機宛に送信します。
    • 受信したパケットの値に応じで DO1-4, PWM1-4 に設定します。

    アクトの使い方

    必要なTWELITEと配線例

    役割
    親機TWELITE DIP
    最低限 M1=GND, DI1:ボタン, DO1:LEDの配線をしておく。
    子機TWELITE DIP
    最低限 M1=オープン, DI1:ボタン, DO1:LEDの配線をしておく。
    配線例 (AI1-AI4は省略可)

    配線例 (AI1-AI4は省略可)

    アクトの解説

    宣言部

    インクルード

    // use twelite mwx c++ template library
    #include <TWELITE>
    #include <NWK_SIMPLE>
    #include <BRD_APPTWELITE>
    #include <STG_STD>

    全てのアクトで<TWELITE>をインクルードします。ここでは、シンプルネットワーク <NWK_SIMPLE> とボードサポート <BRD_APPTWELITE>をインクルードしておきます。

    またインタラクティブモードを追加するために <STG_STD> をインクルードしています。

    その他

    /*** 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;
    // logical id
    uint8_t LID = 0;
    
    /*** function prototype */
    MWX_APIRET transmit();
    void receive();
    
    /*** application defs */
    const char APP_FOURCHAR[] = "BAT1";
    
    // sensor values
    uint16_t au16AI[5];
    uint8_t u8DI_BM;
    • サンプルアクト共通宣言
    • 長めの処理を関数化しているため、そのプロトタイプ宣言(送信と受信)
    • アプリケーション中のデータ保持するための変数

    setup()

    void setup() {
    	/*** SETUP section */
    	// init vars
    	for(auto&& x : au16AI) x = 0xFFFF;
    	u8DI_BM = 0xFF;
    
    	// load board and settings
    	auto&& set = the_twelite.settings.use<STG_STD>();
    	auto&& brd = the_twelite.board.use<BRD_APPTWELITE>();
    	auto&& nwk = the_twelite.network.use<NWK_SIMPLE>();
    
    	// settings: configure items
    	set << SETTINGS::appname("BRD_APPTWELITE");
    	set << SETTINGS::appid_default(DEFAULT_APP_ID); // set default appID
    	set << SETTINGS::ch_default(DEFAULT_CHANNEL); // set default channel
    	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.
    	LID = set.u8devid(); // logical ID
    
    	// 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)
    
    	if (brd.get_M1()) { LID = 0; }
    
    	// Register Network
    	nwk << set // apply settings (LID and retry)
    			;
    
    	// if M1 pin is set, force parent device (LID=0)
    	nwk << NWK_SIMPLE::logical_id(LID); // write logical id again.
    
    	/*** BEGIN section */
    	// start ADC capture
    	Analogue.setup(true, ANALOGUE::KICK_BY_TIMER0); // setup analogue read (check every 16ms)
    	Analogue.begin(pack_bits(
    						BRD_APPTWELITE::PIN_AI1,
    						BRD_APPTWELITE::PIN_AI2,
    						BRD_APPTWELITE::PIN_AI3,
    						BRD_APPTWELITE::PIN_AI4,
    				   		PIN_ANALOGUE::VCC)); // _start continuous adc capture.
    
    	// Timer setup
    	Timer0.begin(32, true); // 32hz timer
    
    	// start button check
    	Buttons.setup(5); // init button manager with 5 history table.
    	Buttons.begin(pack_bits(
    						BRD_APPTWELITE::PIN_DI1,
    						BRD_APPTWELITE::PIN_DI2,
    						BRD_APPTWELITE::PIN_DI3,
    						BRD_APPTWELITE::PIN_DI4),
    					5, 		// history count
    					4);  	// tick delta (change is detected by 5*4=20ms consequtive same values)
    
    
    	the_twelite.begin(); // start twelite!
    
    	/*** INIT message */
    	Serial 	<< "--- BRD_APPTWELITE ---" << mwx::crlf;
    	Serial	<< format("-- app:x%08x/ch:%d/lid:%d"
    					, the_twelite.get_appid()
    					, the_twelite.get_channel()
    					, nwk.get_config().u8Lid
    				)
    			<< mwx::crlf;
    	Serial 	<< format("-- pw:%d/retry:%d/opt:x%08x"
    					, the_twelite.get_tx_power()
    					, nwk.get_config().u8RetryDefault
    					, OPT_BITS
    			)
    			<< mwx::crlf;
    }

    大まかな流れは、各部の初期設定、各部の開始となっています。

    各種ビヘイビアオブジェクトの登録

    	auto&& set = the_twelite.settings.use<STG_STD>();
    	auto&& brd = the_twelite.board.use<BRD_APPTWELITE>();
    	auto&& nwk = the_twelite.network.use<NWK_SIMPLE>();

    システムの振る舞いを決めるためのビヘイビアオブジェクトを登録します。インタラクティブモードの設定管理で合ったり、ボードサポート、また無線パケットのネットワーク記述です。

    インタラクティブモードの設定

    // インタラクティブモードの初期化
    auto&& set = the_twelite.settings.use<STG_STD>();
    
    set << SETTINGS::appname("BRD_APPTWELITE");
    set << SETTINGS::appid_default(DEFAULT_APP_ID); // set default appID
    set << SETTINGS::ch_default(DEFAULT_CHANNEL); // set default channel
    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.
    LID = set.u8devid(); // logical ID;
    

    インタラクティブモードの初期化を行います。まずsetオブジェクトを取得しています。続いて以下の処理を行っています。

    • アプリケーション名を"BRD_APPTWELITE"に設定(メニューで利用される)
    • デフォルトのアプリケーションIDとチャネル値を書き換える
    • 不要な項目を削除する
    • set.reload()により保存された設定値を読み出す
    • OPT_BITSLIDの値を変数にコピーする

    以下は画面例です。+ + + と + を3回、0.2 秒から 1 秒の間をあけて入力するとインタラクティブモード画面を出すことが出来ます。

    [CONFIG/BRD_APPTWELITE:0/SID=8XXYYYYY]
    a: (0x1234ABCD) Application ID [HEX:32bit]
    i: (        13) Device ID [1-100,etc]
    c: (        13) Channel [11-26]
    x: (      0x03) RF Power/Retry [HEX:8bit]
    o: (0x00000000) Option Bits [HEX:32bit]
    
     [ESC]:Back [!]:Reset System [M]:Extr Menu
    

    the_twelite

    このオブジェクトはTWENETの中核としてふるまいます。

    auto&& brd = the_twelite.board.use<BRD_APPTWELITE>();

    ボードの登録(このアクトでは<BRD_APPTWELITE>を登録しています)。以下のように use の後に <> で登録したいボードの名前を指定します。

    ユニバーサル参照(auto&&)にて得られた戻り値として、参照型でのボードオブジェクトが得られます。このオブジェクトにはボード特有の操作や定義が含まれます。以下ではボードオブジェクトを用い、M1ピンの状態を確認しています。M1ピンがLOWであれば、LID=0、つまり親機アドレスと設定します。

    	if (brd.get_M1()) { LID = 0; }

    the_twelite を動作させるには初期設定が必要です。アプリケーションIDや無線チャネルの設定は必須といえます。

    	// the twelite main class
    	the_twelite
    		<< set
    		<< TWENET::rx_when_idle();  // open receive circuit (if not set, it can't listen packts from others)
    

    the_twelite に設定を反映するには << を用います。

    • setはインタラクティブモードから読み出した設定の一部(アプリケーションIDや無線チャネルなど)反映させます。反映される項目は<STG_STD>の解説を参照してください。
    • TWENET::rx_when_idle() 受信回路をオープンにする指定です。

    次にネットワークを登録します。

    auto&& nwk = the_twelite.network.use<NWK_SIMPLE>();
    nwk << set;
    nwk << NWK_SIMPLE::logical_id(LID);

    1行目は、ボードの登録と同じ書き方で <> には <NWK_SIMPLE>を指定します。

    2,3行目は、<NWK_SIMPLE>の設定です。先にインタラクティブモードの設定値を反映させます。反映される項目はLIDと再送回数です。このアプリケーションではM1ピンの状態によってLID=0にする場合があるため、3行目で再度LIDを設定しています。

    Analogue

    ADC(アナログディジタルコンバータ)を取り扱うクラスオブジェクトです。

    Analogue.setup(true, ANALOGUE::KICK_BY_TIMER0);

    初期化Analogue.setup()で行います。パラメータのtrueはADC回路の安定までその場で待つ指定です。2番目のパラメータは、ADCの開始をTimer0に同期して行う指定です。

    	Analogue.begin(pack_bits(
    						BRD_APPTWELITE::PIN_AI1,
    						BRD_APPTWELITE::PIN_AI2,
    						BRD_APPTWELITE::PIN_AI3,
    						BRD_APPTWELITE::PIN_AI4,
    				   	PIN_ANALOGUE::VCC));

    ADCを開始するにはAnalogue.begin()を呼びます。パラメータはADC対象のピンに対応するビットマップです。

    ビットマップを指定するのにpack_bits()関数を用います。可変数引数の関数で、各引数には1を設定するビット位置を指定します。例えばpack_bits(1,3,5)なら2進数で 101010の値が戻ります。この関数はconstexpr指定があるため、パラメータが定数のみであれば定数に展開されます。

    パラメータと指定されるBRD_APPTWELITE::にはPIN_AI1..4が定義されています。App_Tweliteで用いるAI1..AI4に対応します。AI1=ADC1, AI2=DIO0, AI3=ADC2, AI4=DIO2 と割り当てられています。PIN_ANALOGUE::にはADCで利用できるピンの一覧が定義されています。

    Buttons

    DIO (ディジタル入力) の値の変化を検出します。Buttonsでは、メカ式のボタンのチャタリング(摺動)の影響を軽減するため、一定回数同じ値が検出されてから、値の変化とします。

    Buttons.setup(5);

    初期化は Buttons.setup()で行います。パラメータの 5 は、値の確定に必要な検出回数ですが、設定可能な最大値を指定します。内部的にはこの数値をもとに内部メモリの確保を行っています。

    Buttons.begin(pack_bits(
    						BRD_APPTWELITE::PIN_DI1,
    						BRD_APPTWELITE::PIN_DI2,
    						BRD_APPTWELITE::PIN_DI3,
    						BRD_APPTWELITE::PIN_DI4),
    					5, 		// history count
    					4);  	// tick delta
    

    開始は Buttons.begin() で行います。1番目のパラメータは検出対象のDIOです。BRD_APPTWELITE::に定義されるPIN_DI1-4 (DI1-DI4) を指定しています。2番めのパラメータは状態を確定するのに必要な検出回数です。3番めのパラメータは検出間隔です。4を指定しているので4msごとに5回連続で同じ値が検出できた時点で、HIGH, LOWの状態が確定します。

    Timer0

    Timer0.begin(32, true); // 32hz timer
    

    App_Twelite ではアプリケーションの制御をタイマー起点で行っているため、このアクトでも同じようにタイマー割り込み・イベントを動作させます。もちろん1msごとに動作しているシステムのTickTimerを用いても構いません。

    上記の例の1番目のパラメータはタイマーの周波数で32Hzを指定しています。2番目のパラメータをtrueにするとソフトウェア割り込みが有効になります。

    Timer0.begin()を呼び出したあと、タイマーが稼働します。

    the_tweliteの動作開始

    the_twelite.begin(); // start twelite!
    

    setup() 関数の末尾で the_twelite.begin() を実行しています。

    Serial

    Serial オブジェクトは、初期化や開始手続きなく利用できます。

    	Serial 	<< "--- BRD_APPTWELITE ---" << mwx::crlf;
    	Serial	<< format("-- app:x%08x/ch:%d/lid:%d"
    					, the_twelite.get_appid()
    					, the_twelite.get_channel()
    					, nwk.get_config().u8Lid
    				)
    			<< mwx::crlf;
    	Serial 	<< format("-- pw:%d/retry:%d/opt:x%08x"
    					, the_twelite.get_tx_power()
    					, nwk.get_config().u8RetryDefault
    					, OPT_BITS
    			)
    			<< mwx::crlf;

    このサンプルでは始動時のメッセージとしていくつかのシステム設定値を表示しています。Serialオブジェクトには const char* 型の文字列や、int型(他の整数型はNG)、printf()とほぼ同じ振る舞いをするformat()、改行文字を出力するcrlfなどを<<演算子に与えます。

    loop()

    ループ関数は TWENET ライブラリのメインループからコールバック関数として呼び出されます。ここでは、利用するオブジェクトが available になるのを待って、その処理を行うのが基本的な記述です。ここではアクトで使用されているいくつかのオブジェクトの利用について解説します。

    /*** loop procedure (called every event) */
    void loop() {
    	if (Buttons.available()) {
    		uint32_t bp, bc;
    		Buttons.read(bp, bc);
    
    		u8DI_BM = uint8_t(collect_bits(bp,
    							BRD_APPTWELITE::PIN_DI4,   // bit3
    							BRD_APPTWELITE::PIN_DI3,   // bit2
    							BRD_APPTWELITE::PIN_DI2,   // bit1
    							BRD_APPTWELITE::PIN_DI1)); // bit0
    
    		transmit();
    	}
    
    	if (Analogue.available()) {
    		au16AI[0] = Analogue.read(PIN_ANALOGUE::VCC);
    		au16AI[1] = Analogue.read_raw(BRD_APPTWELITE::PIN_AI1);
    		au16AI[2] = Analogue.read_raw(BRD_APPTWELITE::PIN_AI2);
    		au16AI[3] = Analogue.read_raw(BRD_APPTWELITE::PIN_AI3);
    		au16AI[4] = Analogue.read_raw(BRD_APPTWELITE::PIN_AI4);
    	}
    
    	if (Timer0.available()) {
    		static uint8_t u16ct;
    		u16ct++;
    
    		if (u8DI_BM != 0xFF && au16AI[0] != 0xFFFF) { // finished the first capture
    			if ((u16ct % 32) == 0) { // every 32ticks of Timer0
    				transmit();
    			}
    		}
    	}
    }

    Buttons

    DIO(ディジタルIO)の入力変化を検出したタイミングで available になり、Buttons.read()により読み出します。

    	if (Buttons.available()) {
    		uint32_t bp, bc;
    		Buttons.read(bp, bc);

    1番目のパラメータは、現在のDIOのHIGH/LOWのビットマップで、bit0から順番にDIO0,1,2,.. と並びます。例えば DIO12 であれば bp & (1UL << 12) を評価すれば HIGH / LOW が判定できます。ビットが1になっているものがHIGHになります。

    次にビットマップから値を取り出してu8DI_BMに格納しています。ここではMWXライブラリで用意したcollect_bits()関数を用いています。

    u8DI_BM = uint8_t(collect_bits(bp,
    		BRD_APPTWELITE::PIN_DI4,   // bit3
    		BRD_APPTWELITE::PIN_DI3,   // bit2
    		BRD_APPTWELITE::PIN_DI2,   // bit1
    		BRD_APPTWELITE::PIN_DI1)); // bit0
    
    /* collect_bits は以下の処理を行います。
    u8DI_BM = 0;
    if (bp & (1UL << BRD_APPTWELITE::PIN_DI1)) u8DI_BM |= 1;
    if (bp & (1UL << BRD_APPTWELITE::PIN_DI2)) u8DI_BM |= 2;
    if (bp & (1UL << BRD_APPTWELITE::PIN_DI3)) u8DI_BM |= 4;
    if (bp & (1UL << BRD_APPTWELITE::PIN_DI4)) u8DI_BM |= 8;
    */

    collect_bits() は、上述のpack_bits()と同様のビット位置の整数値を引数とします。可変数引数の関数で、必要な数だけパラメータを並べます。上記の処理では bit0 は DI1、bit1 は DI2、bit2 は DI3、bit3 は DI4の値としてu8DI_BMに格納しています。

    App_Twelite では、DI1から4に変化があった場合に無線送信しますので、Buttons.available()を起点に送信処理を行います。transmit()処理の内容は後述します。

    transmit();

    Analogue

    ADCのアナログディジタル変換が終了した直後のloop()で available になります。次の ADC が開始するまでは、データは直前に取得されたものとして読み出すことが出来ます。

    ADCのアナログディジタル変換が終了した直後のloop() available になります。次の ADC が開始するまでは、データは直前に取得されたものとして読み出すことが出来ます。

    ADC値を読むには Analogue.read() または Analogue.read_raw() メソッドを用います。read()はmVに変換した値、read_raw()0..1023 のADC値となります。パラメータにはADCのピン番号を指定します。ADCのピン番号はPIN_ANALOGUE::BRD_APPTWELITE::に定義されているので、こちらを利用しています。

    Timer0

    Timer0は32Hzで動作しています。タイマー割り込みが発生直後の loop() で available になります。つまり、秒32回の処理をします。ここでは、ちょうど1秒になったところで送信処理をしています。

    if (Timer0.available()) {
    	static uint8_t u16ct;
    	u16ct++;
    
    	if (u8DI_BM != 0xFF && au16AI[0] != 0xFFFF) { // finished the first capture
    		if ((u16ct % 32) == 0) { // every 32ticks of Timer0
    			transmit();
    		}
    	}
    }

    AppTweliteでは約1秒おきに定期送信を行っています。Timer0がavailableになったときにu16ctをインクリメントします。このカウンタ値をもとに、32回カウントが終わればtransmit()を呼び出し無線パケットを送信しています。

    u8DI_BMau16AI[]の値判定は、初期化直後かどうかの判定です。まだDI1..DI4やAI1..AI4の値が格納されていない場合は何もしません。

    transmit()

    無線パケットの送信要求をTWENETに行う関数です。本関数が終了した時点では、まだ無線パケットの処理は行われません。実際に送信が完了するのは、送信パラメータ次第ですが、数ms後以降になります。ここでは代表的な送信要求方法について解説します。

    MWX_APIRET transmit() {
    	if (auto&& pkt = the_twelite.network.use<NWK_SIMPLE>().prepare_tx_packet()) {
    	  auto&& set = the_twelite.settings.use<STG_STD>();
    		if (!set.is_screen_opened()) {
    			Serial << "..DI=" << format("%04b ", u8DI_BM);
    			Serial << format("ADC=%04d/%04d/%04d/%04d ", au16AI[1], au16AI[2], au16AI[3], au16AI[4]);
    			Serial << "Vcc=" << format("%04d ", au16AI[0]);
    			Serial << " --> transmit" << mwx::crlf;
    		}
    
    		// set tx packet behavior
    		pkt << tx_addr(u8devid == 0 ? 0xFE : 0x00)  // 0..0xFF (LID 0:parent, FE:child w/ no id, FF:LID broad cast), 0x8XXXXXXX (long address)
    			<< tx_retry(0x1) // set retry (0x1 send two times in total)
    			<< tx_packet_delay(0,50,10); // send packet w/ delay (send first packet with randomized delay from 100 to 200ms, repeat every 20ms)
    
    		// prepare packet payload
    		pack_bytes(pkt.get_payload() // set payload data objects.
    			, make_pair(APP_FOURCHAR, 4) // string should be paired with length explicitly.
    			, uint8_t(u8DI_BM)
    		);
    
    		for (auto&& x : au16AI) {
    			pack_bytes(pkt.get_payload(), uint16_t(x)); // adc values
    		}
    
    		// do transmit
    		return pkt.transmit();
    	}
    	return MWX_APIRET(false, 0);
    }

    関数プロトタイプ

    MWX_APIRET transmit()

    MWX_APIRETuint32_t型のデータメンバを持つ戻り値を取り扱うクラスです。MSB(bit31)が成功失敗、それ以外が戻り値として利用するものです。

    ネットワークオブジェクトとパケットオブジェクトの取得

    	if (auto&& pkt = the_twelite.network.use<NWK_SIMPLE>().prepare_tx_packet()) {

    ネットワークオブジェクトをthe_twelite.network.use<NWK_SIMPLE>()で取得します。そのオブジェクトを用いて.prepare_tx_packet()によりpktオブジェクトを取得します。

    ここではif文の条件判定式の中で宣言しています。宣言したpktオブジェクトはif節の終わりまで有効です。pktオブジェクトはbool型の応答をし、ここではTWENETの送信要求キューに空きがあって送信要求を受け付ける場合にtrue、空きがない場合にfalseとなります。

    インタラクティブモード画面表示中の表示抑制

    auto&& set = the_twelite.settings.use<STG_STD>();
    if (!set.is_screen_opened()) {
        //インタラクティブモード画面中ではない!
    }

    インタラクティブモードの画面が表示されているときは、画面出力を抑制します。

    パケットの送信設定

    pkt << tx_addr(u8devid == 0 ? 0xFE : 0x00)  // 0..0xFF (LID 0:parent, FE:child w/ no id, FF:LID broad cast), 0x8XXXXXXX (long address)
    		<< tx_retry(0x1) // set retry (0x3 send four times in total)
    		<< tx_packet_delay(0,50,10); // send packet w/ delay (send first packet with randomized delay from 100 to 200ms, repeat every 20ms)
    

    パケットの設定はthe_tweliteの初期化設定のように<<演算子を用いて行います。

    • tx_addr() パラメータに送信先アドレスを指定します。0x00なら自分が子機で親機宛に、0xFEなら自分が親機で任意の子機宛のブロードキャストという意味です。
    • tx_retry() パラメータに再送回数を指定します。例の1は再送回数が1回、つまり合計2回パケットを送ります。無線パケット1回のみの送信では条件が良くても数%程度の失敗はあります。
    • tx_packet_delay() 送信遅延を設定します。一つ目のパラメータは、送信開始までの最低待ち時間、2番目が最長の待ち時間です。この場合は送信要求を発行後におよそ0msから50msの間で送信を開始します。3番目が再送間隔です。最初のパケットが送信されてから10ms置きに再送を行うという意味です。

    パケットのデータペイロード

    ペイロードは積載物という意味ですが、無線パケットでは「送りたいデータ本体」という意味でよく使われます。無線パケットのデータにはデータ本体以外にもアドレス情報などいくつかの補助情報が含まれます。

    送受信を正しく行うために、データペイロードのデータ並び順を意識するようにしてください。ここでは以下のようなデータ順とします。このデータ順に合わせてデータペイロードを構築します。

    # 先頭バイトのインデックス: データ型 : バイト数 : 内容
    
    00: uint8_t[4] : 4 : 4文字識別子
    04: uint8_t    : 1 : DI1..4のビットマップ
    06: uint16_t   : 2 : Vccの電圧値
    08: uint16_t   : 2 : AI1のADC値 (0..1023)
    10: uint16_t   : 2 : AI2のADC値 (0..1023)
    12: uint16_t   : 2 : AI3のADC値 (0..1023)
    14: uint16_t   : 2 : AI4のADC値 (0..1023)
    

    上記のデータペイロードのデータ構造を実際に構築してみます。データペイロードは pkt.get_payload() により simplbuf<uint8_t> 型のコンテナとして参照できます。このコンテナに上記の仕様に基づいてデータを構築します。

    auto&& payl = pkt.get_payload();
    payl.reserve(16); // 16バイトにリサイズ
    payl[00] = APP_FOURCHAR[0];
    payl[01] = APP_FOURCHAR[1];
    ...
    payl[08] = (au16AI[0] & 0xFF00) >> 8; //Vcc
    payl[09] = (au16AI[0] & 0xFF);
    ...
    payl[14] = (au16AI[4] & 0xFF00) >> 8; // AI4
    payl[15] = (au16AI[4] & 0xFF);

    上記のように記述できますがMWXライブラリでは、データペイロード構築のための補助関数pack_bytes()を用意しています。

    // prepare packet payload
    pack_bytes(pkt.get_payload() // set payload data objects.
    	, make_pair(APP_FOURCHAR, 4) // string should be paired with length explicitly.
    	, uint8_t(u8DI_BM)
    );
    
    for (auto&& x : au16AI) {
    	pack_bytes(pkt.get_payload(), uint16_t(x)); // adc values
    }

    pack_bytes()の最初のパラメータはコンテナを指定します。この場合はpkt.get_payload()です。

    そのあとのパラメータは可変数引数でpack_bytes()で対応する型の値を必要な数だけ指定します。pack_bytes()は内部で.push_back()メソッドを呼び出して末尾に指定した値を追記していきます。

    3行目のmake_pair()は標準ライブラリの関数でstd::pairを生成します。文字列型の混乱(具体的にはペイロードの格納時にヌル文字を含めるか含めないか)を避けるための指定です。make_pair()の1番目のパラメータに文字列型(char*uint8_t*型、uint8_t[]など)を指定します。2番目のパラメータはペイロードへの格納バイト数です。

    4行目はuint8_t型でDI1..DI4のビットマップを書き込みます。

    7-9行目ではau16AI配列の値を順に書き込んでいます。この値はuint16_t型で2バイトですが、ビッグエンディアンの並びで書き込みます。

    これでパケットの準備は終わりです。あとは、送信要求を行います。

    return pkt.transmit();

    パケットを送信するにはpktオブジェクトのpkt.transmit()メソッドを用います。戻り値としてMWX_APIRET型を返していますが、このアクトでは使っていません。

    on_rx_packet()

    無線パケットが受信できたときは、受信イベントとしてon_rx_packet()が呼び出されます。

    ここでは、相手方から伝えられたDI1..DI4の値とAI1..AI4の値を、自身のDO1..DO4とPWM1..PWM4に設定します。

    void on_rx_packet(packet_rx& rx, bool_t &handled) {
    	auto&& set = the_twelite.settings.use<STG_STD>();
    
    	Serial << format("..receive(%08x/%d) : ", rx.get_addr_src_long(), rx.get_addr_src_lid());
    
      // expand the packet payload
    	char fourchars[5]{};
    	auto&& np = expand_bytes(rx.get_payload().begin(), rx.get_payload().end()
    		, make_pair((uint8_t*)fourchars, 4)  // 4bytes of msg
        );
    
    	// check header
    	if (strncmp(APP_FOURCHAR, fourchars, 4)) { return; }
    
    	// read rest of payload
    	uint8_t u8DI_BM_remote = 0xff;
    	uint16_t au16AI_remote[5];
    	expand_bytes(np, rx.get_payload().end()
    		, u8DI_BM_remote
    		, au16AI_remote[0]
    		, au16AI_remote[1]
    		, au16AI_remote[2]
    		, au16AI_remote[3]
    		, au16AI_remote[4]
    	);
    
    	Serial << format("DI:%04b", u8DI_BM_remote & 0x0F);
    	for (auto&& x : au16AI_remote) {
    		Serial << format("/%04d", x);
    	}
    	Serial << mwx::crlf;
    
    	// set local DO
    	digitalWrite(BRD_APPTWELITE::PIN_DO1, (u8DI_BM_remote & 1) ? HIGH : LOW);
    	digitalWrite(BRD_APPTWELITE::PIN_DO2, (u8DI_BM_remote & 2) ? HIGH : LOW);
    	digitalWrite(BRD_APPTWELITE::PIN_DO3, (u8DI_BM_remote & 4) ? HIGH : LOW);
    	digitalWrite(BRD_APPTWELITE::PIN_DO4, (u8DI_BM_remote & 8) ? HIGH : LOW);
    
    	// set local PWM : duty is set 0..1024, so 1023 is set 1024.
    	Timer1.change_duty(au16AI_remote[1] == 1023 ? 1024 : au16AI_remote[1]);
    	Timer2.change_duty(au16AI_remote[2] == 1023 ? 1024 : au16AI_remote[2]);
    	Timer3.change_duty(au16AI_remote[3] == 1023 ? 1024 : au16AI_remote[3]);
    	Timer4.change_duty(au16AI_remote[4] == 1023 ? 1024 : au16AI_remote[4]);
    }

    関数プロトタイプ

    void on_rx_packet(packet_rx& rx, bool_t &handled)

    受信パケットのデータrxをパラメータとして渡されます。rxから無線パケットのアドレス情報やデータペイロードにアクセスします。パラメータhandledは通常利用しません。

    送信元アドレスの表示

    if (!set.is_screen_opened()) {
       Serial << format("..receive(%08x/%d) : ",
          rx.get_addr_src_long(), rx.get_addr_src_lid());
    }

    受信パケットデータには、送信元のアドレス(32bitのロングアドレスと8bitの論理アドレス)などの情報を参照しています。インタラクティブモード画面が表示されているときは出力を抑制します。

    パケットの識別

    MWXライブラリにはtransmit()の時に使ったpack_bytes()の対になる関数expand_bytes()が用意されています。

    char fourchars[5]{};
    auto&& np = expand_bytes(rx.get_payload().begin(), rx.get_payload().end()
    	, make_pair((uint8_t*)fourchars, 4)  // 4bytes of msg
      );

    1行目ではデータ格納のためのchar型の配列を宣言しています。サイズが5バイトなのは末尾にヌル文字を含め、文字出力などでの利便性を挙げるためです。末尾の{}は初期化の指定で、5バイト目を0にすれば良いのですが、ここでは配列全体をデフォルトの方法で初期化、つまり0にしています。

    2行目でexpand_bytes()により4バイト文字列を取り出しています。パラメータにコンテナ型を指定しない理由は、この続きを読み出すための読み出し位置を把握する必要があるためです。1番目のパラメータでコンテナの先頭イテレータ(uint8_t*ポインタ)を指定します。.begin()メソッドにより取得できます。2番目のパラメータはコンテナの末尾の次を指すイテレータで.end()メソッドで取得できます。2番目はコンテナの末尾を超えた読み出しを行わないようにするためです。

    3番目に読み出す変数を指定しますが、ここでもmake_pair()によって文字列配列とサイズのペアを指定します。

    読み出した4バイト文字列の識別子が、このアクトで指定した識別子と異なる場合は、このパケットを処理しません。

    if (strncmp(APP_FOURCHAR, fourchars, 4)) { return; }

    データペイロードの取得

    DI1..DI4の値とAI1..AI4の値を別の変数に格納します。

    	// read rest of payload
    	uint8_t u8DI_BM_remote = 0xff;
    	uint16_t au16AI_remote[5];
    	expand_bytes(np, rx.get_payload().end()
    		, u8DI_BM_remote
    		, au16AI_remote[0]
    		, au16AI_remote[1]
    		, au16AI_remote[2]
    		, au16AI_remote[3]
    		, au16AI_remote[4]
    	);

    先ほどのexpand_bytes()の戻り値npを1番目のパラメータにしています。先に読み取った4バイト文字列識別子の次から読み出す指定です。2番目のパラメータは同様です。

    3番目以降のパラメータはデータペイロードの並びに一致した型の変数を、送り側のデータ構造と同じ順番で並べています。この処理が終われば、指定した変数にペイロードから読み出した値が格納されます。

    取得したデータの表示

    確認のためシリアルポートへ出力します。インタラクティブモード画面が表示されているときは出力は抑制します。

    auto&& set = the_twelite.settings.use<STG_STD>();
    ...
    Serial << format("DI:%04b", u8DI_BM_remote & 0x0F);
    for (auto&& x : au16AI_remote) {
    	Serial << format("/%04d", x);
    }
    Serial << mwx::crlf;

    数値のフォーマット出力が必要になるのでformat()を用いています。>>演算子向けにprintf()と同じ構文を利用できるようにしたヘルパークラスですが、引数の数が4つまでに制限されています。(Serial.printfmt()には引数の数の制限がありません。)

    3行目の "DI:%04b" は"DI:0010"のようにDI1..DI4のビットマップを4桁で表示します。

    5行目の"/%04d"は"/3280/0010/0512/1023/1023"のように Vcc/AI1..AI4の値を順に整数で出力します。

    7行目のmwx::crlfは改行文字列を出力します。

    信号の出力

    これで必要なデータの展開が終わったので、あとはボード上のDO1..DO4とPWM1..PWM4の値を変更します。

    // set local DO
    digitalWrite(BRD_APPTWELITE::PIN_DO1, (u8DI_BM_remote & 1) ? HIGH : LOW);
    digitalWrite(BRD_APPTWELITE::PIN_DO2, (u8DI_BM_remote & 2) ? HIGH : LOW);
    digitalWrite(BRD_APPTWELITE::PIN_DO3, (u8DI_BM_remote & 4) ? HIGH : LOW);
    digitalWrite(BRD_APPTWELITE::PIN_DO4, (u8DI_BM_remote & 8) ? HIGH : LOW);
    
    // set local PWM : duty is set 0..1024, so 1023 is set 1024.
    Timer1.change_duty(au16AI_remote[1] == 1023 ? 1024 : au16AI_remote[1]);
    Timer2.change_duty(au16AI_remote[2] == 1023 ? 1024 : au16AI_remote[2]);
    Timer3.change_duty(au16AI_remote[3] == 1023 ? 1024 : au16AI_remote[3]);
    Timer4.change_duty(au16AI_remote[4] == 1023 ? 1024 : au16AI_remote[4]);

    digitalWrite()はディジタル出力の値を変更します。1番目のパラメータはピン番号で、2番目はHIGH(VCCレベル)かLOW(GNDレベル)を指定します。

    Timer?.change_duty()はPWM出力のデューティ比を変更します。パラメータにデューティ比 0..1024 を指定します。最大値が1023でないことに注意してください。ライブラリ内で実行される割り算のコストが大きいため、2のべき乗である1024を最大値としています。0にするとGNDレベル、1024にするとVCCレベル相当の出力になります。