# 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`](/master-2/references/basics/tweutils/simplebuffer/smplbuf_byte.md)の参照型を戻します。

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

```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`を読み出しています。


---

# Agent Instructions: Querying This Documentation

If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.

Perform an HTTP GET request on the current page URL with the `ask` query parameter:

```
GET https://mwm5.twelite.info/master-2/getting-started/using-library/parser.md?ask=<question>
```

The question should be specific, self-contained, and written in natural language.
The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
