# Parser

TWELITE 無線マイコンから UART (シリアルポート)経由での電文書式を解釈します。電文書式は、可読性・エラー検出などを目的として、伝送したいデータ列に対して所定の変換を行います。

ここではアスキー形式の解釈を行う`AsciiParser`について解説します。

### アスキー形式

アスキー形式は、バイナリで構成されたデータ列を文字列で表現する方法です。TWELITE無線マイコンでは最も良く用いられる形式です。

例えばバイト列で `00A01301FF123456` をアスキー形式で表現すると、以下のようになります。先頭は `:` で `B1` がチェックサム、終端は `[CR:0x0d][LF:0x0a]` となります。

> `:00A01301FF123456B1[CR][LF]`

終端のチェックサムを省略できます。チェックサムからCRLFの系列を`X`に置き換えます。文字化けによる誤ったデータ系列には弱くなりますが、実験などでデータを送付したいときに便利です。

> `:00A01301FF123456X`

#### 定義

| ====== | 元データのバイト数 | バイト数 | 解説                                                                                                                                                                                                                                    |
| ------ | :-------: | :--: | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| ヘッダ    |           |   1  | `:`(0x3A) コロンを指定します。                                                                                                                                                                                                                  |
| データ部   |     N     |  2N  | <p>元データの各バイトをアスキー文字列２文字（A-F は大文字）で表現します。<br>例えば 0x1F は <code>1</code> (0x31) <code>F</code> (0x46) と表現します。</p>                                                                                                                        |
| チェックサム |           |   2  | <p>データ部の各バイトの和を８ビット幅で計算し２の補数をとります。つまりデータ部の各バイトの総和＋チェックサムバイトを８ビット幅で計算すると０になります。<br>チェックサムバイトをアスキー文字列２文字で表現します。<br>例えば <code>00A01301FF123456</code> では 0x00 + 0xA0 + ... + 0x56 = 0x4F となり、この二の補数は0xB1 です。(つまり 0x4F + 0xB1 = 0x00)</p> |
| フッタ    |           |   2  | \[CR] (0x0D) \[LF] (0x0A) を指定する。                                                                                                                                                                                                      |

## AsciiParser の使用方法

### オブジェクトの生成

```cpp
// serial parser
AsciiParser parse_ascii(256);
```

上記の例では `parser_ascii` というオブジェクトを内部バッファ256バイトで生成しています。このバッファサイズは、書式の解釈後に必要なバイト数です。

{% hint style="info" %}
ASCII形式では実際のバイト数の約２倍の書式になります。例えば書式が200バイトの系列の場合は、実データは約100バイトになります。
{% endhint %}

無線パケットの最大格納バイト数が100バイト強であるため、この例では余裕をもって256バイトのバッファとしています。

### バイト列の解釈

このAsciiParserは１バイトずつの処理を行います。シリアルポートからは１バイトずつデータが到着するためです。

```cpp
while (Serial.available()) {
		int c = Serial.read();
		parse_ascii << char_t(c);
		
		if (parse_ascii) {
		 		// completed!
		}
}
```

上記例では、シリアルポートから１バイト読み出しては`parse_acsii`に１バイトずつ<<演算子を用いて読み込ませています。

直後の`if(pars_ascii)`での判定は、**アスキー形式の系列が正しく解釈できたかどうか**を判定しています。

### データ列の取り出し

```cpp
if (parse_ascii) {
    auto&& p = parse_ascii.get_payload();
}
```

解釈済みの系列は`get_payload()`メソッドにて取得できます。`get_payload()`は[`SmplBuf_Byte`](https://mwm5.twelite.info/master-2/references/basics/tweutils/simplebuffer/smplbuf_byte)の参照型を戻します。

以下のように配列にアクセスできます。

```cpp
auto&& p = parse_ascii.get_payload();

if (p[0] == 0x80) { ... } // 最初の要素
int len = p.length();     // 要素数
for (auto&& x : p) {      // 各要素にアクセス
  Serial.print(x, HEX);
  Serial.print(" "); }
```

## データ列の識別と対応オブジェクトの取得

### パケットの生成

```cpp
auto&& pkt = newTwePacket(parse_ascii.get_payload());

E_PKT pkt_typ = identify_packet_type(pkt);

if (pkt_typ == E_PKT::PKT_PAL) {
  // TWELITE PAL (App_PAL)
} else {
  // unknown packet type
}
```

`newTwePacket()`は入力されたデータ系列を解釈して、`spTwePacket`型のオブジェクトを生成します。このオブジェクトは`TwePacket`型のデータを格納するスマートポインタ`std::shared_ptr`で、メモリ管理を簡素化することができます。

パケット種別は`E_PKT`列挙体で定義されています。ここではTWELITE PALの出力形式`PKT_PAL`の解釈を行います。

{% hint style="info" %}
`std::shared_ptr`の利用は、パケット情報を他にコピーして利用する場合を想定しています。例えば履歴配列に保存して、過去の情報を参照するような場合です。

オブジェクトは参照カウンタにより管理されていて、所有者が0になれば破棄されます。
{% endhint %}

解釈されたパケットの種別は`identify_packet_type()`により判別します。パケット種別は`E_PKT`列挙体で定義されています。

### PALパケットの解釈

```cpp
if (pkt == E_PKT::PKT_PAL) {
    auto&& pal = refTwePacketPal(pkt);
    
    if (pal.u8palpcb == E_PAL_PCB::MAG) {
      // for MAG
    } else
    if (pal.u8palpcb == E_PAL_PCB::AMB) {
      // for AMB
    }
}
```

解釈したパケットが`E_PKT::PKT_PAL`と判定された場合は、`refTwePacketPal()`により`TwePacketPal`型として参照できます。上記コード例ではユニバーサル参照`auto&&`を用いて型名を推測させています。

### PALボード種別解釈

```cpp
if (pal.u8palpcb == E_PAL_PCB::MAG) {
    PalMag mag = pal.get_PalMag();
    
    if (mag.u8MagStat == 0) {
       // closed
    } else {
       // opened
    }
}
```

PALのボード種別に応じたオブジェクトを生成して、ボード種別特有のデータにアクセスすることが出来ます。

上記の例では、MAG(OPEN-CLOSE SENSE PAL)のオブジェクト`mag`を生成しています。ここではオープンクローズに応じた分岐に`mag.u8MagStat`を読み出しています。
