TWILITE の「超簡単！標準アプリ（App_Twelite）」の 送信処理を調べる


「TWILITE の「超簡単！標準アプリ（App_Twelite）」の通信と受信処理を調べてみる」
https://qiita.com/nanbuwks/items/cf6647ef648efc80a106

では、「超簡単！標準アプリ（App_Twelite）」(以下「簡単アプリ」)の5種類の通信について調べました。ここではその5種類の送信がソースでどう記述されているか調べます。

# ステータス0x81:IO状態の送信

変化割り込みおよび、定期送信に使われている。

```

/** @ingroup MASTER
 * IO 情報を送信します。
 *
 * - IO状態の変化、および１秒置きの定期送時に呼び出されます。
 *
 * - Packet 構造
 *   - OCTET: 識別ヘッダ(APP ID より生成)
 *   - OCTET: プロトコルバージョン(バージョン間干渉しないための識別子)
 *   - OCTET: 送信元論理ID
 *   - BE_DWORD: 送信元のシリアル番号
 *   - OCTET: 宛先論理ID
 *   - BE_WORD: 送信タイムスタンプ (64fps カウンタの値の下１６ビット, 約1000秒で１周する)
 *   - OCTET: 中継フラグ(中継したパケットは１、最初の送信なら０を格納）
 *   - BE_WORD: 電圧
 *   - OCTET: 温度 (int8型)  ※ TODO: 値が不正確。ADC 値から温度への変換式がメーカより開示されないため。
 *   - OCTET: ボタン (LSB から順に SW1 ... SW4, 1=Lo), 0x80ビットは通常送信の識別用フラグ
 *   - OCTET: ボタン変化 (LSB から順に SW1 ... SW4, 1=変化)
 *   - OCTET: ADC1 (MSB から 8bit)
 *   - OCTET: ADC2 (MSB から 8bit)
 *   - OCTET: ADC3 (MSB から 8bit)
 *   - OCTET: ADC4 (MSB から 8bit)
 *   - OCTET: ADC 詳細部 (MSB b8b7b6b5b4b3b2b1 LSB とすると b2b1 が ADC1 の LSB 2bit, 以下同様)
 *
 * - ADC 値の復元方法
 *   - 8bit ADC値の場合 16倍すると mV 値になります。
 *   - ADC詳細部の2bitを追加し 10bit ADC 値の場合 4 倍すると mV 値なります。
 *
 * @returns -1:ERROR, 0..255 CBID
 */
static int16 i16TransmitIoData(uint8 u8Quick, bool_t bRegular) {
	if (IS_APPCONF_ROLE_SILENT_MODE())
		return -1;

	int16 i16Ret = -1;
	tsTxDataApp sTx;
	memset(&sTx, 0, sizeof(sTx));

	uint8 *q = sTx.auData;

	int i;

	// ペイロードを構成
	S_OCTET(sAppData.u8AppIdentifier);
	S_OCTET(APP_PROTOCOL_VERSION);
	S_OCTET(sAppData.u8AppLogicalId); // アプリケーション論理アドレス
	S_BE_DWORD(ToCoNet_u32GetSerial());  // シリアル番号
	S_OCTET(IS_LOGICAL_ID_PARENT(sAppData.u8AppLogicalId) ? LOGICAL_ID_CHILDREN : LOGICAL_ID_PARENT); // 宛先

	S_BE_WORD((sAppData.u32CtTimer0 & 0x7FFF) + (u8Quick == TX_NODELAY_AND_QUICK_BIT ? 0x8000 : 0)); // タイムスタンプ

	// bQuick 転送する場合は MSB をセットし、優先パケットである処理を行う
	S_OCTET(0); // 中継フラグ

	S_BE_WORD(sAppData.sIOData_now.u16Volt); // 電圧

	// チップ内温度センサーの予定だったが・・・


・・・


	// ボタンのビットマップ使用フラグ (１度でもLoになったポートは１になる）
	{
		uint8 i, c = 0x0;

#ifdef USE_MONOSTICK
		for (i = 0; i < 4; i++) {
			if( i != 2 ){
				c |= (sAppData.sIOData_now.u32BtmUsed & (1UL << au8PortTbl_DIn[i])) ? (1 << i) : 0;
			}
		}
#else
		for (i = 0; i < 4; i++) {
			c |= (sAppData.sIOData_now.u32BtmUsed & (1UL << au8PortTbl_DIn[i])) ? (1 << i) : 0;
		}
#endif

		if (u8Quick == TX_NODELAY_AND_RESP_BIT) c |= 0x80;

		S_OCTET(c);
	}

	// ADC 部のエンコード
	uint8 u8LSBs = 0;
	for (i = 0; i < 4; i++) {
		// MSB 部分 (10bit目～3bit目まで)
		uint16 u16v = sAppData.sIOData_now.au16InputADC[i];
		u16v >>= 2; // ADC 値は 0...2400mV

		uint8 u8MSB = (u16v >> 2) & 0xFF;
		S_OCTET(u8MSB);

		// 下2bitを u8LSBs に詰める
		u8LSBs >>= 2;
		u8LSBs |= ((u16v << 6) & 0xC0); //
	}
	S_OCTET(u8LSBs); // 詳細ビット部分を記録

	sTx.u8Len = q - sTx.auData; // パケット長
	sTx.u8Cmd = TOCONET_PACKET_CMD_APP_USER_IO_DATA; // パケット種別

	// 送信する
	sTx.u32DstAddr = TOCONET_MAC_ADDR_BROADCAST; // ブロードキャスト
	sTx.u8Retry = sAppData.u8StandardTxRetry; // 再送

	// フレームカウントとコールバック識別子の指定
	sAppData.u16TxFrame++;
	sTx.u8Seq = (sAppData.u16TxFrame & 0xFF);
	sTx.u8CbId = sTx.u8Seq;

	{
		/* MAC モードでは細かい指定が可能 */
		sTx.bAckReq = FALSE;
		sTx.u32SrcAddr = sToCoNet_AppContext.u16ShortAddress;

		// 送信タイミングの調整
		switch (u8Quick) {
		case TX_NODELAY:
		case TX_NODELAY_AND_QUICK_BIT:
		case TX_NODELAY_AND_RESP_BIT:
			sTx.u16RetryDur = 0; // 再送間隔
			sTx.u16DelayMin = 0; // すみやかに送信する
			sTx.u16DelayMax = 0; // すみやかに送信する
			break;
		case TX_SMALLDELAY:
		case TX_REPONDING:
			sTx.u16RetryDur = 0; // 再送間隔
			sTx.u16DelayMin = 4; // 衝突を抑制するため送信タイミングを遅らせる
			sTx.u16DelayMax = 8; // 衝突を抑制するため送信タイミングを遅らせる
			break;
		default:
			sTx.u16RetryDur = 4; // 再送間隔
			sTx.u16DelayMin = 0; // 衝突を抑制するため送信タイミングを遅らせる
			sTx.u16DelayMax = 16; // 衝突を抑制するため送信タイミングにブレを作る(最大16ms)
			break;
		}

#ifdef USE_SLOW_TX
	    //ここから
	    if (u8Quick == 0x10) {
	      sTx.u8Retry = sAppData.u8StandardTxRetry; // 再送回数を３回とする
	      sTx.u16DelayMax = 100; // 初回送信は送信要求発行時～100ms の間（ランダムで決まる）
	      sTx.u16RetryDur = 20; // 20ms おきに再送する
	    }
	    //ここまで
#endif

		// 送信API
		if (ToCoNet_bMacTxReq(&sTx)) {
			if (sTx.u16DelayMax == 0) {
				// すぐに送る場合
				ToCoNet_Tx_vProcessQueue();
			}

			i16Ret = sTx.u8CbId;
			sAppData.sIOData_now.u32TxLastTick = u32TickCount_ms;
		}
	}

	return i16Ret;
}
```



## 定期送信

ステータス0x81:IO状態の送信 のうち定期送信は
void vProcessEvCorePwr(tsEvent *pEv, teEvent eEvent, uint32 u32evarg) {
のうちの402行付近で処理されている

```

			// レギュラー送信
			if (!bCond && (sAppData.u16CtRndCt == 0)) {
				if (!IS_APPCONF_OPT_REGULAR_PACKET_NO_TRANSMIT()) {
					bCond = TRUE;
					bRegular = TRUE;
				}
			}

			// 送信
			if (bCond) {
				// デバッグ出力
				DBGOUT(5,
						"A(%02d/%04d)%d%d: v=%04d A1=%04d/%04d A2=%04d/%04d B=%d%d%d%d %08x"LB,
						sAppData.u32CtTimer0, u32TickCount_ms & 8191,
						sAppData.bUpdatedAdc ? 1 : 0,
						sAppData.sIOData_now.u32BtmChanged ? 1 : 0,
						sAppData.sIOData_now.u16Volt,
						sAppData.sIOData_now.au16InputADC[0],
						sAppData.sIOData_now.au16InputPWMDuty[0] >> 2,
						sAppData.sIOData_now.au16InputADC[1],
						sAppData.sIOData_now.au16InputPWMDuty[1] >> 2,
						sAppData.sIOData_now.au8Input[0] & 1,
						sAppData.sIOData_now.au8Input[1] & 1,
						sAppData.sIOData_now.au8Input[2] & 1,
						sAppData.sIOData_now.au8Input[3] & 1,
						sAppData.sIOData_now.u32BtmBitmap);

				// 低遅延送信が必要かどうかの判定
				bool_t bQuick = FALSE;
				if (sAppData.sIOData_now.u32BtmChanged
						&& (sAppData.sFlash.sData.u32Opt
								& E_APPCONF_OPT_LOW_LATENCY_INPUT)) {
					bQuick = TX_NODELAY_AND_QUICK_BIT; // 低レイテンシ専用
				}

				// 送信要求
				sAppData.sIOData_now.i16TxCbId = i16TransmitIoData(bQuick, bRegular);
```

## IO状態の送信を任意に実行してみる

以下で送信できる

```

		bool bQuick=TX_NODELAY;
		bool bRegular = TRUE;
		sAppData.sIOData_now.i16TxCbId = i16TransmitIoData(bQuick, bRegular);
```

# コマンド0x80,コマンド0x01,コマンド0x88:シリアル送信部分

シリアルUARTから入力された電文の送信を行います。
また、I2Cの操作コマンド0x88の返答である、ステータス0x89にも使われています。


```

/** @ingroup MASTER
 * シリアルメッセージの送信要求を行います。１パケットを分割して送信します。
 *
 * - Packet 構造
 *   - OCTET    : 識別ヘッダ(APP ID より生成)
 *   - OCTET    : プロトコルバージョン(バージョン間干渉しないための識別子)
 *   - OCTET    : 送信元個体識別論理ID
 *   - BE_DWORD : 送信元シリアル番号
 *   - OCTET    : 送信先シリアル番号
 *   - OCTET    : 送信タイムスタンプ (64fps カウンタの値の下１６ビット, 約1000秒で１周する)
 *   - OCTET    : 送信フラグ(リピータ用)
 *   - OCTET    : 要求番号
 *   - OCTET    : パケット数(total)
 *   - OCTET    : パケット番号 (0...total-1)
 *   - BE_WORD  : 本パケットのバッファ位置
 *   - OCTET    : 本パケットのデータ長
 *
 * @param p ペイロードのデータへのポインタ
 * @param u16len ペイロード長
 * @param bRelay 中継フラグ TRUE:中継する
 * @return -1:失敗, 0:成功
 */
static int16 i16TransmitSerMsg(uint8 *p, uint16 u16len, uint32 u32AddrSrc,
		uint8 u8AddrSrc, uint8 u8AddrDst, uint8 u8RelayLv, uint8 u8Req) {
	if (IS_APPCONF_ROLE_SILENT_MODE())
		return -1;

	// パケットを分割して送信する。
	tsTxDataApp sTx;
	memset(&sTx, 0, sizeof(sTx));
	uint8 *q; // for S_??? macros

	// 処理中のチェック（処理中なら送信せず失敗）
	if (sSerSeqTx.bWaitComplete) {
		return -1;
	}

	// sSerSeqTx は分割パケットの管理構造体
	sSerSeqTx.u8IdSender = sAppData.u8AppLogicalId;
	sSerSeqTx.u8IdReceiver = u8AddrDst;

	sSerSeqTx.u8PktNum = (u16len - 1) / SERCMD_SER_PKTLEN + 1;
	sSerSeqTx.u16DataLen = u16len;
	sSerSeqTx.u8Seq = sSerSeqTx.u8SeqNext; // パケットのシーケンス番号（アプリでは使用しない）
	sSerSeqTx.u8SeqNext = sSerSeqTx.u8Seq + sSerSeqTx.u8PktNum; // 次のシーケンス番号（予め計算しておく）
	sSerSeqTx.u8ReqNum = u8Req; // パケットの要求番号（この番号で送信系列を弁別する）
	sSerSeqTx.bWaitComplete = TRUE;
	sSerSeqTx.u32Tick = u32TickCount_ms;
	memset(sSerSeqTx.bPktStatus, 0, sizeof(sSerSeqTx.bPktStatus));

	DBGOUT(3, "* >>> Transmit(req=%d) Tick=%d <<<" LB, sSerSeqTx.u8ReqNum,
			u32TickCount_ms & 65535);

	sTx.u8Cmd = TOCONET_PACKET_CMD_APP_DATA; // data packet.
	sTx.u8Retry = sAppData.u8StandardTxRetry;
	sTx.u16RetryDur = sSerSeqTx.u8PktNum * 10; // application retry

	int i;
	for (i = 0; i < sSerSeqTx.u8PktNum; i++) {
		q = sTx.auData;
		sTx.u8Seq = sSerSeqTx.u8Seq + i;
		sTx.u8CbId = sTx.u8Seq; // callback will reported with this ID

		// ペイロードを構成
		S_OCTET(sAppData.u8AppIdentifier);
		S_OCTET(APP_PROTOCOL_VERSION);
		S_OCTET(u8AddrSrc); // アプリケーション論理アドレス
		S_BE_DWORD(u32AddrSrc);  // シリアル番号
		S_OCTET(sSerSeqTx.u8IdReceiver); // 宛先
		S_BE_WORD(sAppData.u32CtTimer0 & 0xFFFF); // タイムスタンプ

		S_OCTET(u8RelayLv); // 中継レベル

		S_OCTET(sSerSeqTx.u8ReqNum); // request number
		S_OCTET(sSerSeqTx.u8PktNum); // total packets
		S_OCTET(i); // packet number
		S_BE_WORD(i * SERCMD_SER_PKTLEN); // offset

		uint8 u8len_data =
				(u16len >= SERCMD_SER_PKTLEN) ? SERCMD_SER_PKTLEN : u16len;
		S_OCTET(u8len_data);

		memcpy(q, p, u8len_data);
		q += u8len_data;

		sTx.u8Len = q - sTx.auData;

		// あて先など
		sTx.u32DstAddr = TOCONET_MAC_ADDR_BROADCAST; // ブロードキャスト

		if (sAppData.eNwkMode == E_NWKMODE_MAC_DIRECT) {
			sTx.u32SrcAddr = sToCoNet_AppContext.u16ShortAddress;
			sTx.bAckReq = FALSE;
			sTx.u8Retry = sAppData.u8StandardTxRetry;

			ToCoNet_bMacTxReq(&sTx);
		}

		p += u8len_data;
		u16len -= SERCMD_SER_PKTLEN;
	}

	return 0;
}


```
## 任意の電文をコマンド0x01で送信する

以下で送信できる。

```

 tsModbusCmd a;
 a.au8data = au8SerBuffTx;
 a.u16len=5;
 au8SerBuffTx[0]=0;
 au8SerBuffTx[1]=1;
 au8SerBuffTx[2]=2;
 au8SerBuffTx[3]=3;
 au8SerBuffTx[4]=4;
 vProcessSerialCmd(&a);
```
これを子機から送信すると、親機で :0001020304F6 と表示される。
シリアルから入力の際はコロン、F6、CR、LFが必要だが、 vProcessSerialCmdに渡すデータには必要ない。




# I2Cの応答:ステータス0x89

static void vProcessI2CCommand(uint8 *p, uint16 u16len, uint8 u8AddrSrc) {
内で、シリアル送信を呼び出しています。