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

もとのページに戻る

2024-11-14 現在

クラス

各種クラス

1 - MWX_APIRET

戻り値クラス
32bit型をラップしたAPI戻り値クラス。MSB(bit31)は失敗、成功のフラグ。bit0..30は戻り値を格納するために使用します。
class MWX_APIRET {
	uint32_t _code;
public:
	MWX_APIRET() : _code(0) {}
	MWX_APIRET(bool b) {
	  _code = b ? 0x80000000 : 0;
  }
	MWX_APIRET(bool b, uint32_t val) {
		_code = (b ? 0x80000000 : 0) + (val & 0x7fffffff);
	}
	inline bool is_success() const { return ((_code & 0x80000000) != 0); }
	inline bool is_fail() const { return ((_code & 0x80000000) == 0); }
	inline uint32_t get_value() const { return _code & 0x7fffffff; }
	inline operator uint32_t() const { return get_value(); }
	inline operator bool() const { return is_success(); }
};

コンストラクタ

MWX_APIRET()
MWX_APIRET(bool b)
MWX_APIRET(bool b, uint32_t val)

デフォルトコンストラクタはfalse,0の組み合わせで構築します。

またbool型とuint32_t型をパラメータとする明示的な構築も可能です。

bool型のコンストラクタを実装しているため、以下のようにtrue/falseを用いることができます。

MWX_APIRET myfunc() {
  if (...) return true;
  else false;
}

メソッド

is_success(), operator bool()

inline bool is_success()
inline operator bool()

MSBに1がセットされていればtrueを返す。

inline bool is_fail()

MSBが0の場合にtrueを返す。

inline uint32_t get_value()
inline operator uint32_t()

bit0..30の値部を取得する。

2 - alloc

メモリ確保
コンテナクラス(smplbuf, smplque)のテンプレート引数として指定し、内部で利用するメモリの確保または領域指定します。
クラス名内容
alloc_attach<T>すでにあるバッファを指定する
alloc_local<T, int N>Nバイトのバッファを内部に静的確保する
alloc_heap<T>指定したサイズをヒープに確保する

alloc_attachalloc_heapではメモリ確保クラスに応じた初期化メソッド (init_???())を実行する必要があります。

初期化

void attach(T* p, int n) // alloc_attach
void init_local()        // alloc_local
void init_heap(int n)    // alloc_heap

バッファーp・サイズnで初期化します。

メソッド

alloc_size()

uint16_t alloc_size()

バッファのサイズを返す。

_is_attach(), _is_local(), _is_heap()

想定したallocクラスと異なるメソッド呼び出し記述に対して、static_assertのように、コンパイルエラーを発生させるためのメソッドです。

3 - axis_xyzt

加速度データのコンテナクラス
3軸の加速度センサーの値を格納するための構造体ですが、格納したデータの利便性を向上するための手続きを追加しています。
struct axis_xyzt {
    int16_t x;
    int16_t y;
    int16_t z;
    uint16_t t;
};

get_axis_{x,y,z}_iter()

/*戻り型は長いテンプレート型名なのでauto&&と記載します*/
auto&& get_axis_x_iter(Iter p)
auto&& get_axis_y_iter(Iter p)
auto&& get_axis_z_iter(Iter p)

axis_xyztを格納したコンテナクラスのイテレータをパラメータとして、X, Y, Z 軸のいずれかの要素にアクセスするイテレータを生成します。

以下の例では、buf.begin(), buf.end()をX軸用のイテレータとしてアルゴリズムstd::minmax_elementに用いています。

##include <algorithm>

void myfunc() {
  // コンテナクラス
  smplbuf_local<axis_xyzt, 10> buf;

  // テスト用にデータを投入
  buf[0] = { 1, 2, 3, 4 };
  buf[1] = { 2, 3, 4, 5 };
  ...

  // 最大、最小値を得るアルゴリズム
  auto&& minmax = std::minmax_element(
    get_axis_x_iter(buf.begin()),
    get_axis_x_iter(buf.end()));

  Serial << "min=" << int(*minmax.first)
        << ",max=" << int(*minmax.second) << mwx::crlf;
}

get_axis_{x,y,z}()

/*戻り型は長いテンプレート型名なのでauto&&と記載します*/
auto&& get_axis_x(T& c)
auto&& get_axis_y(T& c)
auto&& get_axis_z(T& c)

axis_xyztを格納したコンテナクラスのXYZ軸のいずれかの軸を取り出した仮想的なコンテナクラスを生成する関数です。この生成したクラスにはbegin()end()メソッドのみ実装されています。このbegin()end()メソッドで取得できるイテレータは前節get_axis_{x,y,z}_iter()のイテレータと同じものになります。

##include <algorithm>

void myfunc() {
  // コンテナクラス
  smplbuf_local<axis_xyzt, 10> buf;

  // テスト用にデータを投入
  buf[0] = { 1, 2, 3, 4 };
  buf[1] = { 2, 3, 4, 5 };
  ...

  // キューの中の X 軸を取り出す
  auto&& vx = get_axis_x(que);

  // 範囲for文の利用
  for (auto&& e : vx) { Serial << int(e) << ','; }

  // 最大、最小値を得るアルゴリズム
  auto&& minmax = std::minmax_element(
      vx.begin(), vx.end());

  Serial << "min=" << int(*minmax.first)
        << ",max=" << int(*minmax.second) << mwx::crlf;
}

4 - packet_rx

受信パケット
このクラスはTWENETのtsRxDataApp構造体のラッパークラスです。

このクラスオブジェクトは、ビヘイビアのコールバック関数またはon_rx_packets()により取得できます。

packet_rxでは、特にパケットのデータペイロードをsmplbufコンテナで取り扱えるようにし、expand_bytes()などのユーティリティ関数によりペイロードの解釈記述を簡素化しています。

メソッド

get_payload()

smplbuf_u8_attach& get_payload()

パケットのデータペイロードを取得する。

get_psRxDataApp()

const tsRxDataApp* get_psRxDataApp()

TWENET Cライブラリの受信構造体を得る。

get_length()

uint8_t get_length()

ペイロードのデータ長を返す。.get_payload().size()と同じ値になる。

get_lqi()

uint8_t get_lqi()

LQI値 (Link Quality Indicator)を得る。

get_addr_src_long(), get_addr_src_lid()

uint32_t get_addr_src_long()
uint8_t get_addr_src_lid()

送信元のアドレスを得る。

get_addr_src_long()は送信元のシリアル番号で、MSB(bit31)が必ず1になります。

get_addr_src_lid()は送信元の論理IDで0x00-0xFEまでの値をとります(<NWK_SIMPLE>で指定する論理IDです)。

get_addr_dst()

uint32_t get_addr_dst()

宛先アドレスを得ます。

宛先アドレスは、送信元で指定され、宛先の種別によって値の範囲が変わります。

解説
MSB(bit31)がセットされている宛先としてシリアル番号を指定しています。
0x00-0xFF宛先として論理ID(8bit)が指定されています。

is_secure_pkt()

bool is_secure_pkt()

暗号化パケットの場合は true を返し、平文の時はfalse を返します。

get_network_type()

uint8_t get_network_type()

ネットワークビヘイビアで識別されるパケットのネットワークタイプを返す。

解説
mwx::NETWORK::LAYERED<NWK_LAYERED> からのパケット
mwx::NETWORK::SIMPLE<NWK_SIMPLE> からのパケット
mwx::NETWORK::NONEネットワークを介さないパケット (App_Tweliteなど)
その他エラーまたは識別できないパケット

5 - packet_tx

送信パケット
このクラスはTWENET CライブラリのtsTxDataApp構造体のラッパクラスで、このクラスをベースとした派生クラスのオブジェクトをネットワークビヘイビアまたはon_tx_comp()により取得します。
if (auto&& pkt = the_twelite.network.use<NWK_SIMPLE>().prepare_tx_packet()) {
	pkt << tx_addr(0x00)
		<< tx_retry(0x1)
		<< tx_packet_delay(0,50,10);

	pack_bytes(pkt.get_payload()
		, make_pair("APP1", 4)
		, uint8_t(u8DI_BM)
	);

  pkt.transmit();
}

オブジェクトの生成

ネットワークビヘイビアの .prepare_tx_packet() によって行います。

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

上記の例ではthe_twelite.network.use<NWK_SIMPLE>()によってネットワークビヘイビアのオブジェクトを取り出します。このオブジェクトの.prepare_tx_packet() によってオブジェクトpktが生成されます。型名はauto&&で推論されていますがpacket_txの派生クラスになります。

このpktオブジェクトは、まず、()内の条件判定にてtruefalseを返します。falseが返ってくるのは、送信用のキューが一杯でこれ以上要求が追加できない時です。

送信設定

無線パケットには宛先情報など相手に届けるための様々な設定を行います。設定には設定内容を含むオブジェクトを«演算子の右辺値に与えます。

pkt << tx_addr(0x00)
	  << tx_retry(0x1)
  	<< tx_packet_delay(0,50,10);

以下に設定に用いるオブジェクトについて記載します。

tx_addr

tx_addr(uint32_t addr)

宛先アドレスaddrを指定します。宛先アドレスの値については、ネットワークビヘイビアの仕様を参照してください。

  • <NWK_SIMPLE> MSB(bit31=0x80000000)がセットされるアドレスは、無線モジュールのシリアル番号宛という意味になります。 0x00..0xEFは、8bitの論理IDを意味します。0xFEは子機宛(0x01..0xEF)の同報通信(ブロードキャスト)、0xFFは親機子機関係なく同報通信(ブロードキャスト)します。

tx_retry

tx_retry(uint8_t u8count, bool force_retry = false)

再送回数の指定を行います。再送回数はu8countで指定します。force_retryは、送信が成功しようがしまいが、指定回数の再送を行う設定です。

  • <NWK_SIMPLE> ネットワークビヘイビア<NWK_SIMPLE>では、同じ内容のパケットをu8count+1回送信します。 force_retryの設定は無視されます。

tx_packet_delay

tx_packet_delay(uint16_t u16DelayMin,
                uint16_t u16DelayMax,
                uint16_t u16RetryDur)

パケットを送信するまでの遅延と再送間隔を設定します。u16DelayMinu16DelayMaxの2つの値をミリ秒[ms]で指定します。送信要求をしてからこの間のどこかのタイミングで送信を開始します。再送間隔をu16RetryDurの値[ms]で指定します。再送間隔は一定です。

  • <NWK_SIMPLE> この指定は有効です。 最初の送信から1秒を超えて再送され到達した同一パケットについては、新たなパケットが到達したとして重複除外が為されません。再送間隔を長く設定したり、中継でのパケット到達遅延により1秒を超えて同じパケットを受信する場合があります。 重複パケットの処理の設定は<NWK_SIMPLE>ビヘイビアの初期化で設定できます。

tx_process_immediate

tx_process_immediate()

パケット送信を「できるだけ速やかに」実行するように要求する設定です。TWENETでのパケット送信処理は、1msごとに動作するTickTimer起点で行われています。この設定をすることで、要求後速やかにパケット送信要求が処理されます。もちろん、tx_packet_delay(0,0,0)以外の設定では意味がない指定になります。

他のパケット送信処理が行われている場合は、通常の処理になります。

  • <NWK_SIMPLE> この指定は有効です。

tx_ack_required

tx_ack_required()

無線パケット通信では、送信完了後、送信相手先からACK(アック)という短い無線電文を得て、送信成功とする送信方法があります。このオプションを設定することで、ACK付き送信を行います。

  • <NWK_SIMPLE> <NWK_SIMPLE>では、この指定は無効です。コンパイルエラーになります。 <NWK_SIMPLE>は、シンプルに動作する中継ネットワークの実装を目的としており、ACK付きの通信は行いません。

tx_addr_broadcast

tx_addr_broadcast()

ブロードキャストの指定を行います。

  • <NWK_SIMPLE> <NWK_SIMPLE>では、この指定は無効です。コンパイルエラーになります。 替わりに宛先アドレスtx_addr(0xFF) (ブロードキャスト)またはtx_addr(0xFE)(子機宛のブロードキャスト)を指定します。

tx_packet_type_id

tx_packet_type_id(uint8_t)

0..7の指定ができるTWENET無線パケットのタイプIDを指定します。

  • <NWK_SIMPLE> <NWK_SIMPLE>では、この指定は無効です。コンパイルエラーになります。 <NWK_SIMPLE>ではタイプIDを内部的に使用しています。ユーザは使用できません。

6 - serparser

シリアル書式入出力 (mwx::serial_parser)
シリアル書式の入出力のために用います。内部に解釈済みのバイナリ系列を保持するバッファを持ち、入力時は1バイトずつ系列を読み出し書式に従い内部バッファに格納し、系列の解釈が完了した時点で完了状態になるものです。反対に出力時は内部バッファから所定の出力書式に従いバッファを出力します。

メモリバッファ取り扱い方法(alloc)に応じて3種類のクラス名が定義されています。

// serparser_attach : 既存のバッファを用いる
serparser_attach

// serparser : Nバイトのバッファを内部に確保する
serparser_local<N>

// serparser_heap : ヒープ領域にバッファを確保する
serparser_heap

定数(書式種別)

begin()の初期化のパラメータで渡す書式の種別です。ここではアスキー形式とバイナリー形式の2種類があります。

定数種別
uint8_t PARSER::ASCII = 1アスキー形式
uint8_t PARSER::BINARY = 2バイナリー形式

形式について

アスキー形式

アスキー形式は、バイナリで構成されたデータ列を文字列で表現する方法です。

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

:00A01301FF123456B1[CR][LF]

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

:00A01301FF123456X

定義

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

バイナリ形式

バイナリ形式は、バイナリで構成されたデータ列にヘッダとチェックサムを付加して送付する方法です。

例えば 00A01301FF123456をバイナリ形式で表現すると、以下のようになります。

0xA5 0x5A 0x80 0x08 0x00 0xA0 0x13 0x01 0xFF 0x12 0x34 0x56 0x3D

定義

======元データのバイト数形式におけるバイト数解説
ヘッダ20xA5 0x5A を指定します。
データ長2データ長はビッグエンディアン形式の2バイトで、MSB (0x8000) を設定した上、データ部の長さを指定します。
例えばデータ部の長さが 8 バイトなら0x80 0x08 を指定します。
データ部NN元データを指定します。
チェックサム1データ部の各バイトの XOR を計算します。
例えばデータ部が 00A01301FF123456なら 0x00 xor 0xA0 xor … 0x56 = 0x3D となります。
フッタ(1)チェックサムが事実上の終端です。無線モジュールからの出力では 0x04 (EOT) が付加されます。

メソッド

宣言, begin()

// serparser_attach : 既存のバッファを用いる
serparser_attach p1;

uint8_t buff[128];
p1.begin(ARSER::ASCII, buff, 0, 128);


// serparser : Nバイトのバッファを内部に確保する
serparser p2<128>;
p2.begin(PARSER::ASCII);


// serparser_heap : ヒープ領域にバッファを確保する
serparser_heap p3;
p3.begin(PARSER::ASCII, 128);

宣言にはメモリの確保クラスを指定します。この指定は煩雑であるため、上述のように別名定義を行っています。

クラス名(別名定義)
メモリ確保
内容
serparser_attachすでにあるバッファをbegin()にて指定する
serparser_local<N>Nバイトのバッファを内部に確保する
serparser_heapbegin()メソッドのパラメータで指定したサイズをヒープに確保する

メモリ確保クラスに応じたbegin()メソッドを呼び出します。

serparser_attach

void begin(uint8_t ty, uint8_t *p, uint16_t siz, uint16_t max_siz)

tyで指定する形式で、pで指定したバッファを用います。バッファの最大長はmax_sizで、バッファの有効データ長をsizで指定します。

この定義は、特に、データ列を書式出力したい場合に用います(>> 演算子参照)

serparser_local<N> - 内部にバッファを確保する

void begin(uint8_t ty)

tyで指定する形式で初期化を行います。

serparser_heap - ヒープに確保

void begin(uint8_t ty, uint16_t siz)

tyで指定する形式で、sizで指定したサイズをヒープに確保して初期化します。

get_buf()

BUFTYPE& get_buf()

内部バッファを返す。バッファは smplbuf<uint8_t, alloc> 型となります。

parse()

inline bool parse(uint8_t b)

入力文字を処理する。書式入力の入力文字列を1バイト受け取り書式に従い解釈します。例えばASCII書式では:00112233Xのような系列を入力として受け取りますが : 0 0 ... X の順で1バイトずつ入力し、最後の X を入力した時点で書式の解釈を完了し、完了済みと報告します。

parse()のパラメータは入力バイト、戻り値は解釈完了であればtrueを戻します。

while (Serial.available()) {
    int c = Serial.read();

    if (SerialParser.parse(c)) {
        // 書式解釈完了、b に得られたデータ列(smplbuf<uint8_t>)
        auto&& b = SerialParser.get_buf();

        // 以下は得られたデータ列に対する処理を行う
        if (b[0] == 0xcc) {
          // ...
        }
    }
}

operator bool()

operator bool()

trueならparse()により読み出しが完了した状態で、falseなら解釈中となります。

例 (parse()の例は以下のように書き換えられる)

while (Serial.available()) {
    int c = Serial.read();

    SerialParser.parse(c);

    if(SerialParser) {
        // 書式解釈完了、b に得られたデータ列(smplbuf<uint8_t>)
        auto&& b = SerialParser.get_buf();
        // ...
    }
}

<< 演算子

内部バッファを書式形式でストリーム(Serial)に対して出力します。

uint8_t u8buf[] = { 0x11, 0x22, 0x33, 0xaa, 0xbb, 0xcc };

ser_parser pout;
pout.begin(ARSER::ASCII, u8buf, 6, 6); // u8bufの6バイトを指定

Serial << pout;// Serialに書式出力 -> :112233AABBCC??[CR][LF]

7 - pktparser

パケットパーサ
pktparser(parser_packet)は、serparserで変換したバイト列に対して、内容の解釈を行います。
serparser_heap parser_ser;

void setup() {
    // init ser parser (heap alloc)
    parser_ser.begin(PARSER::ASCII, 256);
}

void loop() {
    int c;
    while ((c = Serial.read()) >= 0) {
        parser_ser.parse(c);
        if (parser_ser.available()) {
            // get buffer object
            auto&& payl = parser_ser.get_buf();
            // identify packet type
            auto&& typ = identify_packet_type(payl.begin(), payl.end());

            // if packet type is TWELITE standard 0x81 message
            if (typ == E_PKT::PKT_TWELITE) {
                pktparser pkt; // packet parser object
                // analyze packet data
                typ = pkt.parse<TwePacketTwelite>(payl.begin(), payl.end());

                if (typ != E_PKT::PKT_ERROR) { // success!
                    // get data object
                    auto&& atw = pkt.use<TwePacketTwelite>();

                    // display packet inforamtion
                    Serial << crlf << format("TWELITE: SRC=%08X LQI=%03d "
                        , app.u32addr_src, app.u8lqi);
	                  Serial << " DI1..4="
	                      << atw.DI1 ? 'L' : 'H' << atw.DI2 ? 'L' : 'H'
                        << atw.DI3 ? 'L' : 'H' << atw.DI4 ? 'L' : 'H';
                }
            }
        }
    }
}

上記の例は、標準アプリケーションの0x81メッセージの解釈を行っています。parser_serオブジェクトによりSerialより入力された電文をバイト列に変換します。このバイト列をまずidentify_packet_type()により電文の種別E_PKTを特定します。電文の種別が判定できたら次に.parse<TwePacketTwelite>()により電文を解析します。解析結果はTwePacketTwelite型になりますが、このオブジェクトを取り出す手続きが.use<TwePacketTwelite>()です。TwePacketTwelite型はクラスですが構造体として直接メンバー変数を参照します。

parse<T>

template <class T>
E_PKT parse(const uint8_t* p, const uint8_t* e)

バイト列を解析します。

Tには解析対象のパケット型を指定します。例えば標準アプリケーションの0x81メッセージならTwePacketTweliteを指定します。

peはバイト列の先頭と終端の次を指定します。

戻り値はE_PKT型です。エラーの場合はE_PKT::PKT_ERRORが戻ります。

user<T>

template 
T& use()

解釈したバイト列に対応するパケット型に対応するオブジェクトの参照を返します。事前にparse<T>を実行しエラーがなかった場合に呼び出すせます。

Tparse<T>で実行した型と同じもの、または基本的な情報のみ取得できるTwePacketを指定します。

7.1 - E_PKT

パケット種別定義

以下のパケットに対応します。

名前解説
PKT_ERRORパケット解釈前やパケット種別が特定できないなど、TwePacketには意味のあるデータが格納されていない
PKT_TWELITE標準アプリ App_Twelite の 0x81 コマンドを解釈したもの
PKT_PALTWELITE PALのシリアル形式を解釈したもの
PKT_APPIOリモコンアプリ App_IO UARTメッセージを解釈したもの
PKT_APPUARTシリアル通信アプリ App_UART 拡張書式を解釈したもの。
PKT_APPTAG無線タグアプリApp_TagのUARTメッセージを解釈したもの。センサ固有部分は解釈されずpayloadとしてバイト列を報告します。
PKT_ACT_STDアクト(Act)のサンプルなどで使用される出力書式。

7.2 - identify_packet_type()

パケット種別の判定

idenify_packet_type()

パケットデータのバイト列を入力として、パケットの種別を判定します。戻り値はE_PKTです。

E_PKT identify_packet_type(uint8_t* p, uint8_t u8len)

特定のパケットであると解釈できなかった場合はE_PKT::PKT_ERRORが戻ります。

7.3 - TwePacket

パケット型
パケット型の基底クラスですが、メンバー構造体commonにはアドレス情報など共通情報が含まれます。
class TwePacket {
	public:
		static const E_PKT _pkt_id = E_PKT::PKT_ERROR;

		struct {
			uint32_t tick;     // 解釈実行時のシステム時刻[ms]
			uint32_t src_addr; // 送信元アドレス(シリアル番号)
			uint8_t src_lid;   // 送信元アドレス(論理アドレス)
			uint8_t lqi;       // LQI
			uint16_t volt;     // 電圧[mV]
		} common;
};

7.3.1 - TwePacketTwelite

App_Tweliteからのパケット
TwePacketTweliteクラスは、標準アプリApp_Tweliteの0x81コマンドを解釈したものです。
class TwePacketTwelite : public TwePacket, public DataTwelite { ... };

パケットデータ内の諸情報はparse<TwePacketTwelite>()実行後にパケット情報がDataTweliteに格納されます。

DataTwelite構造体

struct DataTwelite {
		//送信元のシリアル#
		uint32_t u32addr_src;

		// 送信元の論理ID
		uint8_t u8addr_src;

		// 宛先の論理ID
		uint8_t u8addr_dst;

		// 送信時のタイムスタンプ
		uint16_t u16timestamp;

		// 低レイテンシ送信時のフラグ
		bool b_lowlatency_tx;

		// リピート中継回数
		uint16_t u8rpt_cnt;

		// LQI値
		uint16_t u8lqi;

		// DIの状態 (true がアクティブ Lo,GND)
		bool DI1, DI2, DI3, DI4;
		// DIの状態ビットマップ (LSBから順にDI1,2,3,4)
		uint8_t DI_mask;

		// DIアクティブならtrue (過去にアクティブになったことがある)
		bool DI1_active, DI2_active, DI3_active, DI4_active;
		// DIのアクティブビットマップ(LSBから順にDI1,2,3,4)
		uint8_t DI_active_mask;

		// モジュールの電源電圧[mV]
		uint16_t u16Volt;

		// AD値 [mV]
		uint16_t u16Adc1, u16Adc2, u16Adc3, u16Adc4;
		// ADがアクティブ(有効)なら 1 になるビットマップ (LSBから順にAD1,2,3,4)
		uint8_t Adc_active_mask;
};

7.3.2 - TwePacketIO

App_IOからのパケット
TwePacketAppIOクラスは、標準アプリApp_IOのシリアルメッセージ(0x81)を解釈したものです。
class TwePacketAppIO : public TwePacket, public DataAppIO { ... };

パケットデータ内の諸情報はparse<TwePacketIO>()実行後にDataTweliteに格納されます。

DataAppIO構造体

struct DataAppIO {
		//送信元のシリアル#
		uint32_t u32addr_src;

		// 送信元の論理ID
		uint8_t u8addr_src;

		// 宛先の論理ID
		uint8_t u8addr_dst;

		// 送信時のタイムスタンプ
		uint16_t u16timestamp;

		// 低レイテンシ送信時のフラグ
		bool b_lowlatency_tx;

		// リピート中継回数
		uint16_t u8rpt_cnt;

		// LQI値
		uint16_t u8lqi;

		// DIの状態ビットマップ (LSBから順にDI1,2,3,4,...)
		uint8_t DI_mask;

		// DIのアクティブ(使用なら1)ビットマップ(LSBから順にDI1,2,3,4,...)
		uint8_t DI_active_mask;

		// DIが割り込み由来かどうかのビットマップ(LSBから順にDI1,2,3,4,...)
		uint16_t DI_int_mask;
};

7.3.3 - TwePacketUART

App_UARTからのパケット
TwePacketAppUartクラスは、App_UARTの拡張書式を親機・中継機アプリApp_Wingsで受信したときの形式です。
class TwePacketAppUART : public TwePacket, public DataAppUART

パケットデータ内の諸情報はparse<TwePacketUART>()実行後にDataAppUARTに格納されます。

DataAppUART構造体

struct DataAppUART {
		/**
		 * source address (Serial ID)
		 */
		uint32_t u32addr_src;

		/**
		 * source address (Serial ID)
		 */
		uint32_t u32addr_dst;

		/**
		 * source address (logical ID)
		 */
		uint8_t u8addr_src;

		/**
		 * destination address (logical ID)
		 */
		uint8_t u8addr_dst;

		/**
		 * LQI value
		 */
		uint8_t u8lqi;

		/**
		 * Response ID
		 */
		uint8_t u8response_id;

		/**
		 * Payload length
		 */
		uint16_t u16paylen;

		/**
		 * payload
		 */
##if MWX_PARSER_PKT_APPUART_FIXED_BUF == 0
		mwx::smplbuf_u8_attach payload;
##else
		mwx::smplbuf_u8<MWX_PARSER_PKT_APPUART_FIXED_BUF> payload;
##endif
	};

payloadはデータ部分ですが、マクロ定義によってデータ格納の方法が変わります。

MWX_PARSER_PKT_APPUART_FIXED_BUFの値が0としてコンパイルした場合は、payloadはパケット解析を行うバイト列を直接参照します。元のバイト列の値が変更されるとpayload中のデータは破壊されます。

MWX_PARSER_PKT_APPUART_FIXED_BUFの値を0より大きい値として定義した場合は、payloadにはその値(バイト数)のバッファが確保されます。ただしシリアル電文のデータがバッファサイズを超えた場合はparse<TwePacketAppUART>()は失敗しE_PKT::PKT_ERRORを戻します。

7.3.4 - TwePacketPAL

App_PALからのパケット
TwePacketPalクラスは、TWELITE PALのパケットデータを解釈したものです。このクラスはTWELITE PAL(センサーデータなど上り方向)共通に取り扱います。
class TwePacketPal : public TwePacket, public DataPal { ... };

PAL共通データはDataPalに定義されています。

PALの各センサー基板特有のデータを取り出すためのジェネレータ関数を用意しています。

DataPal構造体

PALは接続されるセンサーなどによってパケットデータ構造が異なりますが、DataPalでは共通部のデータ構造を保持します。

struct DataPal {
	uint8_t u8lqi;        // LQI値

	uint32_t u32addr_rpt; // 中継器のアドレス

	uint32_t u32addr_src; // 送信元のアドレス
	uint8_t u8addr_src;   // 送信元の論理アドレス

	uint16_t u16seq;      // シーケンス番号

	E_PAL_PCB u8palpcb;		// PAL基板の種別
	uint8_t u8palpcb_rev;	// PAL基板のレビジョン
	uint8_t u8sensors;		// データに含まれるセンサーデータの数 (MSB=1はエラー)
	uint8_t u8snsdatalen; // センサーデータ長(バイト数)

	union {
		const uint8_t *au8snsdata; // センサーデータ部への参照
		uint8_t _pobj[MWX_PARSER_PKT_APPPAL_FIXED_BUF]; // 各センサーオブジェクト
	};
};

PALのパケットデータ構造は大まかに2つのブロックからなり、全てのPAL共通部と個別のデータ部になります。個別のデータ部は、パケットの解釈を行わずそのまま格納しています。取り扱いを単純化するため32バイトを超えるデータは動的に確保するuptr_snsdataに格納します。

個別のデータ部は、PalBaseをベースクラスに持つ構造体に格納されます。この構造体は、TwePacketPalに定義されるジェネレータ関数により生成されます。

parse<TwePacketPAL>()実行時にMWX_PARSER_PKT_APPPAL_FIXED_BUFに収まるサイズであれば、センサー個別のオブジェクトを生成します。

収まらない場合はau8snsdataに解析時のバイト列の参照が保存されます。この場合、解析に用いたバイト列のデータが書き換えられた場合は、センサー個別のオブジェクトは生成できなくなります。

PalBase

PALの各センサーのデータ構造体はすべてPalBaseを継承します。センサーデータの格納状況u32StoredMaskが含まれます。

	struct PalBase {
		uint32_t u32StoredMask; // 内部的に利用されるデータ取得フラグ
	};

PalEvent

PALイベントは、センサーなどの情報を直接送るのではなく、センサー情報を加工し一定の条件が成立したときに送信される情報です。例えば加速度センサーの静止状態から一定以上の加速度が検出された場合などです。

	struct PalEvent {
		uint8_t b_stored;       // 格納されていたら true
		uint8_t u8event_source; // 予備
		uint8_t u8event_id;     // イベントID
		uint32_t u32event_param;// イベントパラメータ
	};

イベントデータが存在する場合はTwePacketPal.is_PalEvent()trueになることで判定でき、.get_PalEvent()によりPalEventデータ構造を得られます。

ジェネレータ関数

センサーPALの各種データを取り出すためのジェネレータ関数です。

void print_pal(pktparser& pkt) {
	auto&& pal = pkt.use<TwePacketPal>();
	if (pal.is_PalEvent()) {
		PalEvent obj = pal.get_PalEvent();
	} else
	switch(pal.u8palpcb) {
	case E_PAL_PCB::MAG:
	  {
		  // generate pal board specific data structure.
		  PalMag obj = pal.get_PalMag();
	  } break;
  case E_PAL_PCB::AMB:
	  {
		  // generate pal board specific data structure.
		  PalAmb obj = pal.get_PalAmb();
	  } break;
	  ...
	default: ;
	}
}

ジェネレータ関数を利用するには、まずpktがイベントかどうか判定(.is_PalEvent())します。イベントの場合はget_PalEvent()を持ちます。それ以外はu8palpcbに応じてオブジェクトを生成します。

get_PalMag()

PalMag get_PalMag()

// MAG
struct PalMag : public PalBase {
    uint16_t u16Volt;         // モジュール電圧[mV]
    uint8_t u8MagStat;        // 磁気スイッチの状態 [0:磁石なし,1,2]
    uint8_t bRegularTransmit; // MSB flag of u8MagStat
};

.u8palpcb==E_PAL_PCB::MAGの場合、開閉センサーパルのデータPalMagを取り出します。

get_PalAmb()

PalAmb get_PalAmb()

// AMB
struct PalAmb : public PalBase {
    uint16_t u16Volt;       // モジュール電圧[mV]
    int16_t i16Temp;        // 温度(100倍値)
    uint16_t u16Humd;       // 湿度(100倍値)
    uint32_t u32Lumi;       // 照度(Lux相当)
};

.u8palpcb==E_PAL_PCB::AMBの場合、環境センサーパルのデータPalAmbを取り出します。

get_PalMot()

PalMot get_PalMot()

// MOT
struct PalMot : public PalBase {
    uint16_t u16Volt;  // モジュール電圧[mV]
    uint8_t u8samples; // サンプル数
    uint8_t u8sample_rate_code; // サンプルレート (0: 25Hz, 4:100Hz)
    int16_t i16X[16]; // X 軸
    int16_t i16Y[16]; // Y 軸
    int16_t i16Z[16]; // Z 軸
};

.u8palpcb==E_PAL_PCB::MOTの場合、動作センサーパルのデータPalMotを取り出します。

get_PalEvent()

PalEvent get_PalEvent()

// PAL event
struct PalEvent {
    uint8_t b_stored;        // trueならイベント情報あり
    uint8_t u8event_source;  // イベント源
    uint8_t u8event_id;      // イベントID
    uint32_t u32event_param; // 24bit、イベントパラメータ
};

.is_PalEvent()trueの場合PalEvent(PALイベント)を取り出します。

8 - smplbuf

配列構造のコンテナクラス
内部が配列構造のコンテナクラスです。初期化時にバッファの最大サイズを決定しますが、その最大サイズまでの範囲で可変長の配列として振る舞います。
template <typename T, int N> smplbuf_local
template <typename T> smplbuf_attach
template <typename T> smplbuf_heap

smplbufは要素の型Tメモリの確保方法allocで指定したメモリ領域に対して配列の操作を提供するコンテナクラスです。allocの指定は煩雑であるためusingを用いた別名定義が行っています。

オブジェクトの宣言例です。宣言の直後に初期化用のメソッド呼び出しを行います。いずれも初期化直後の最大サイズは128バイトで、サイズは0です。必要に応じてサイズを拡張しながら使用します。

// 配列領域は、クラスメンバー変数の固定配列
smplbuf_local<uint8_t, 128> b1;

// 配列領域は、すでにある領域を参照
uint8_t buf[128];
smplbuf_attach<uint8_t> b2(;

// 配列領域は、ヒープに確保
smplbuf_heap<uint8_t> b3;

// 初期化(グローバル定義の場合はsetup()で行う)
void setup() {
    b1.init_local();
    b2.attach(buf, 0, 128);
    b3.init_heap(128);
}

// 処理関数内
void some_func() {
    smplbuf_local<uint8_t, 128> bl;
    // bl.init_local(); // smplbuf_localがローカル定義の場合は省略可能

    bl.push_back('a');
}

上記のuint8_t型に限り別名定義があります。

template <int N>
smplbuf_u8
// smplbuf<uint8_t, alloc_local<uint8_t, N>>

smplbuf_u8_attach
// smplbuf<uint8_t, alloc_attach<uint8_t>>

smplbuf_u8_heap
// smplbuf<uint8_t, alloc_heap<uint8_t>>

通常の配列のように[]演算子などを用いて要素にアクセスできますし、イテレータを用いたアクセスも可能です。

void begin() { // begin()は起動時1回だけ動作する
  smplbuf_u8<32> b1;
  b1.reserve(5); // 5バイト分利用領域に初期化(b1[0..5]にアクセスできる)

  b1[0] = 1;
  b1[1] = 4;
  b1[2] = 9;
  b1[3] = 16;
  b1[4] = 25;

  for(uint8_t x : b1) { // 暗黙に .begin() .end() を用いたループ
    Serial << int(x) << ",";
  }
}

push_back()メソッドを定義しています。末尾にデータを追記するタイプのアルゴリズムが使用可能になります。

宣言・初期化

smplbuf_local<T,N>()
smplbuf_local<T,N>::init_local()

smplbuf_attach<T>(T* buf, uint16_t size, uint16_t N)
smplbuf_attach<T>::attach(T* buf, uint16_t size, uint16_t N)

smplbuf_heap<T>()
smplbuf_heap<T>::init_heap(uint16_t N)

// 例
// 内部に固定配列
smplbuf_local<uint8_t, 128> b1;
b1.init_local();

// すでにある配列を利用する
uint8_t buf[128];
smplbuf_attach<uint8_t> b2;
b2.attach(buf, 0, 128);

// ヒープに確保する
smplbuf_heap<uint8_t> b3;
b3.init_heap(128);

TでサイズNのコンテナを宣言します。宣言後に初期化のメソッドを呼び出します。

smplbuf_localは、内部に固定配列により領域を確保します。コンストラクタによる初期化も可能です。

smplbuf_attachでは、使用するバッファの先頭ポインタT* bufと配列の初期サイズsizeと最大サイズNを指定します。コンストラクタによる初期化も可能です。

smplbuf_heapは、HEAP領域(解放は不可能だが随時確保可能なメモリ領域)にメモリを確保します。一度確保したら開放できない領域ですので通常はグローバル領域に定義します。領域確保はinit_heap()で行います。コンストラクタによるメモリ確保はできません。必ずinit_heap()を呼び出して利用してください。

初期化子リスト

void in_some_func() {
    smplbuf_local<uint8_t, 5> b1;
    b1.init_local();

    b1 = { 0, 1, 2, 3, 4 };

    smplbuf_local<uint8_t, 5> b2{0, 1, 2, 3, 4};
}

初期化子リスト(イニシャライザリスト){ ... } によるメンバーの初期化をできます。smplbuf_localのローカル宣言でのコンストラクタでの利用を除き、初期化のメソッドを呼び出した後に有効です。

  • 代入演算子の右辺値 (smplbuf_local, smplbuf_attach, smplbuf_heap)
  • コンストラクタ(smplbuf_localのローカル宣言、グローバル宣言は不可)

メソッド

append(), push_back(), pop_back()

inline bool append(T&& c)
inline bool append(const T& c)
inline void push_back(T&& c)
inline void push_back(const T& c)
inline void pop_back()

末尾にメンバーcを追加します。append()の戻り値はboolで、バッファが一杯で追加できないときにfalseが返ります。

pop_back()は末尾のエントリを抹消します。ただしエントリのクリアはしません。

empty(), size(), capacity()

inline bool empty()
inline bool is_end()
inline uint16_t size()
inline uint16_t capacity()

empty()は配列に要素が格納されていない場合にtrueを戻します。is_end()は反対に配列サイズ一杯まで要素が格納されているときにtrueを戻します。

size()は配列の要素数を返します。

capacity()は配列の最大格納数を返します。

reserve(), reserve_head(), redim()

inline bool reserve(uint16_t len)
inline void reserve_head(uint16_t len)
inline void redim(uint16_t len)

reserve()は配列のサイズを拡張します。配列が格納されていない領域はデフォルトで初期化されます。

reserve_hear()は配列の先頭部に指定したサイズの領域を確保します。コンテナオブジェクトからは参照できない領域となります。例えばパケットペイロードのヘッダ部分を読み飛ばした部分配列にアクセスするようなコンテナとして利用できるようにします。確保した領域を戻しすべてアクセスできるようにコンテナを戻すには確保時と同じ負の値を与えます。

redim()は利用領域のサイズを変更します。reserve()と違い、未使用領域の初期化を行いません。

operator []

inline T& operator [] (int i)
inline T operator [] (int i) const

要素にアクセスします。

iに負の値を与えるとバッファー末尾からの要素となります。-1の場合は末尾の要素、-2は末尾から一つ手前となります。

mwx::streamへの出力

uint8_t型の配列オブジェクト(smplbuf<uint8_t, *>)は、mwx::streamの派生オブジェクトに対して、そのまま出力できます。

<< 演算子

template <class L_STRM, class AL>
	mwx::stream<L_STRM>& operator << (
			mwx::stream<L_STRM>& lhs, mwx::_smplbuf<uint8_t, AL>& rhs)

//例
smplbuf_u8<128> buf;
buf.push_back('a');
buf.push_back('b');
buf.push_back('c');

Serial << buf;
// 出力: abc

Serialなどmwx::streamの派生オブジェクトに対して、バイト列を出力します。

to_stream()

inline std::pair<T*, T*> to_stream()

//例
smplbuf_u8<128> buf;
buf.push_back('a');
buf.push_back('b');
buf.push_back('c');

Serial << buf.to_stream();
// 出力:0123

ストリームへの出力目的で利用します。«演算子の実装に用いられています。

mwx::streamでデータ生成

mwx::streamでは<<演算子やprintfmt()メソッドと行ったストリームに対してバイト列を出力するための関数・演算子が定義されています。uint8_t型のsmplbufの配列を出力先と見立ててストリーム出力手続きを行えます。

方法は2種類あります。

8.1 - get_stream_helper()

mwx::stream を使用するためのヘルパーオブジェクト
uint8_t型のsmplbuf配列を参照したstream_helper を経由して、mwx::streamによる演算子やメソッドを用います。
smplbuf_u8<32> b;
auto&& bs = b.get_stream_helper(); // ヘルパーオブジェクト

// データ列の生成
uint8_t FOURCHARS[]={'A', 'B', 'C', 'D'};
bs << FOURCHARS;
bs << ';';
bs << uint32_t(0x30313233); // "0123"
bs << format(";%d", 99);

Serial << b << crlf; // Serialへの出力は smplbuf_u8<32> クラス経由で

//結果: ABCD;0123;99

ヘルパーオブジェクトの型名は長くなるためauto&&により解決しています。このオブジェクトに対して<<演算子などmwx::streamで定義されたインタフェースを利用できます。

生成されたヘルパーオブジェクトbsは生成時に大本の配列bの先頭位置から読み書きを始めます。配列の末尾の場合はappend()によりデータを追加します。読み書きを行うたびに位置は次に移動していきます

ヘルパー関数では読み出し用の>>演算子が利用できます。

//..上例の続き
// ABCD;0123;99 <- bに格納されている

//読み出しデータ格納変数
uint8_t FOURCHARS_READ[4];
uint32_t u32val_read;
uint8_t c_read[2];

// >>演算子で読み出す
bs.rewind();                //ポジションを先頭に巻き戻す
bs >> FOURCHARS_READ;      //4文字
bs >> mwx::null_stream(1); //1文字スキップ
bs >> u32val_read;         //32bitデータ
bs >> mwx::null_stream(1); //1文字スキップ
bs >> c_read;              //2文字

// 結果表示
Serial << crlf << "4chars=" << FOURCHARS_READ;
Serial << crlf << format("32bit val=0x%08x", u32val_read);
Serial << crlf << "2chars=" << c_read;

// 4chars=ABCD
// 32bit val=0x30313233
// 2chars=99

8.2 - smplbuf_strm_u8

直接ストリーム用メソッドを使用することのできる型
uint8_t型のsmplbuf_strm_u8???ストリーム(stream)インタフェースも有しているため、いくつかのストリーム用のメソッドを使用することができます。
// smplbuf_strm_u8<N> : ローカル確保
template <int N> using smplbuf_strm_u8
  = _smplbuf_stream<uint8_t, mwx::alloc_local<uint8_t, N>>;

// smplbuf_strm_u8_attach : 既存バッファへのアタッチ版
using smplbuf_strm_u8_attach
  = mwx::_smplbuf_stream<uint8_t, mwx::alloc_attach<uint8_t>>;

// smplbuf_strm_u8_heap : HEAP確保
using smplbuf_strm_u8_heap
  = mwx::_smplbuf_stream<uint8_t, mwx::alloc_heap<uint8_t>>;

// << 演算子の定義
template <class L_STRM, class ALOC>
mwx::stream<L_STRM>& operator << (
        mwx::stream<L_STRM>& lhs,
        mwx::_smplbuf_stream<uint8_t, ALOC>& rhs)
{
		lhs << rhs.to_stream();
		return lhs;
}

smplbuf_strm_u8<128> sb1;

sb1 << "hello";
sb1 << uint32_t(0x30313233);
sb1 << format("world%d",99);
sb1.printfmt("Z!");

Serial << sb1;
// hello0123world99Z!

9 - smplque

FIFOキュー構造のコンテナクラス
FIFOキュー構造のコンテナクラスです。
template <typename T, int N, class Intr> smplbuf_local
template <typename T, class Intr> smplbuf_attach
template <typename T, class Intr> smplbuf_heap

smplqueは要素の型Tメモリの確保方法allocで指定したメモリ領域に対してFIFOキューの操作を提供するコンテナクラスです。allocの指定は煩雑であるためusingを用いた別名定義が行っています。

宣言時に割り込み禁止設定を行うクラスIntrを登録することが出来ます。このクラスは指定しない場合は、割り込み禁止制御を行わない通常の動作となります。

オブジェクトの宣言例です。宣言の直後に初期化用のメソッド呼び出しを行います。いずれも初期化直後の最大サイズは128バイトで、初期サイズは0で何も格納されていません。最大サイズは変更できません。

void some_func() {

// 内部に固定配列
smplque_local<uint8_t, 128> q1;

// すでにある配列を利用する
uint8_t buf[128];
smplque_attach<uint8_t> q2;

// ヒープに確保する
smplque_heap<uint8_t> q3;

void setup() {
  // グローバル定義のオブジェクトは setup() で初期化
  q1.init_local();
  q2.attach(buf, 128);
  q3.init_heap(128);
}

void some_func() {
  // ローカル定義の smplque_local は init_local() は省略できる
  smplque_local<uint8_t, 128> q_local;
  ..
}

FIFOキューですのでpush(),pop(),front()といったメソッドを用いて操作します。

void begin() { // begin() は起動時1回のみ動作する
	smplque_local<int, 32> q1;

	q1.push(1);
	q1.push(4);
	q1.push(9);
	q1.push(16);
	q1.push(25);

	while(!q1.empty()) {
		Serial << int(q1.front()) << ',';
		q1.pop();
	}
	// output -> 1,4,9,16,25,
}

イテレータによるアクセスも可能です。

void begin() { // begin() は起動時1回のみ動作する
	smplque_local<int, 32> q1;
	q1.init_local();

	q1.push(1);
	q1.push(4);
	q1.push(9);
	q1.push(16);
	q1.push(25);

	// イテレータを利用
	for(int x : q1) {
		Serial << int(x) << ',';
	}

	// STLアルゴリズムの適用
	auto&& minmax = std::minmax_element(q1.begin(), q1.end());
	Serial <<  "min=" << int(*minmax.first)
		     << ",max=" << int(*minmax.second);
	// output -> 1,4,9,16,25,min=1,max=25[]
}

宣言・初期化

smplbuf_local<T,N>
smplbuf_local<T,N>::init_local()

smplbuf_attach<T>
smplbuf_attach<T>::attach(T* buf, uint16_t N)

smplbuf_heap<T>
smplbuf_heap<T>::init_heap(uint16_t N);

//例
// 内部に固定配列
smplque_local<uint8_t, 128> q1;
q1.init_local();

// すでにある配列を利用する
uint8_t buf[128];
smplque_attach<uint8_t> q2;
q2.attach(buf, 128);

// ヒープに確保する
smplque_heap<uint8_t> q3;
q3.init_heap(128);

TでサイズNのコンテナを宣言します。宣言後に初期化のメソッドを呼び出します。

smplque_localは、内部に固定配列により領域を確保します。コンストラクタによる初期化も可能です。

smplque_attachでは、使用するバッファの先頭ポインタT* bufと配列の初期サイズsizeと最大サイズNを指定します。コンストラクタによる初期化も可能です。

smplque_heapは、HEAP領域(解放は不可能だが随時確保可能なメモリ領域)にメモリを確保します。一度確保したら開放できない領域ですので通常はグローバル領域に定義します。領域確保はinit_heap()で行います。コンストラクタによるメモリ確保はできません。必ずinit_heap()を呼び出して利用してください。

メソッド

push(), pop(), front(), back()

inline void push(T&& c)
inline void push(T& c)
inline void pop()
inline T& front()
inline T& back()

inline T& pop_front()

push()はエントリをキューに追加します。

pop()はエントリをキューから抹消します。

front()は先頭のエントリ(一番最初に追加されたもの)を参照します。

back()は末尾のエントリ(一番最後に追加されたもの)を参照します。

pop_front()は先頭のエントリを戻り値として参照し、同時にそのエントリをキューから抹消します。

empty(), size(), is_full()

inline bool empty()
inline bool is_full()
inline uint16_t size()
inline uint16_t capacity()

empty()は配列に要素が格納されていない場合にtrueを戻します。is_full()は反対に配列サイズ一杯まで要素が格納されているときにtrueを戻します。

size()はキューに格納されている要素数を返します。

capacity()はキューの最大格納数を返します。

clear()

inline void clear()

キューのすべての要素を抹消します。

operator []

inline T& operator[] (int i)

要素にアクセスします。0が最初に追加した要素です。

イテレータ

inline smplque::iterator begin()
inline smplque::iterator end()

begin()end()によるイテレータを取得できます。イテレータの先頭はキューの最初に登録した要素です。イテレータを用いることで範囲for文やアルゴリズムが利用できます。

応用としてaxis_xyzt構造体の特定のメンバーに注目したイテレータによるアクセスがあります。

10 - 入出力ストリーム

入出力ストリームを処理する上位クラス
入出力ストリームを処理する上位クラスです。
  • CRTP (Curiously Recurring Template Pattern) 手法を用いたポリモーフィズムにより、いくつかのクラス(Serial, Wire, SPI, smplbuf) にインタフェースを提供します。
    • CRTP では下位クラスは template class Derived : public stream<Derived>;のように定義し、上位クラスからも下位クラスのメソッドを参照します。
  • 本クラスでは print メソッド、<< 演算子などの共通処理の定義を行い、下位クラスで実装した write() メソッドなどを呼び出すことで、仮想関数を用いるのと近い実装を行っています。

インタフェース(下位クラスで実装)

下位クラスでは、以下に列挙する関数を実装します。

available()

int available()

// example
while(Serial.available()) {
  int c = Serial.read();
  // ... any
}

入力が存在する場合は 1、存在しない場合は 0 を返します。

パラメータ解説
戻り値 int0: データなし 1:データあり

flush()

void flush()

// example
Serial.println("long long word .... ");
Serial.flush();

出力をフラッシュ(出力完了まで待つ)します。

read()

int read()

// example
int c;
while (-1 != (c = read())) {
    // any
}

ストリームより1バイトデータを入力します。データが存在しない場合は -1 を戻します。

write()

size_t write(int c)

// example
Serial.write(0x30);

ストリームに1バイト出力します。

パラメータ解説
n出力したい文字。
戻り値 size_t出力が成功すれば 1、失敗すれば 0。

vOutput()

static void vOutput(char out, void* vp)

1バイト出力を行うスタティック関数です。クラスメソッドではないため、メンバー変数等の情報は利用できません。替わりにパラメータとして渡される vp にクラスインスタンスへのポインタを渡します。

このスタティック関数は内部的に利用されfctprintf()の1バイト出力関数として関数ポインタが渡ります。これを用いてprintメソッドなどを実装しています。

パラメータ解説
out出力したい文字
vpクラスインスタンスへのポインタ
通常は、元のクラスにキャストして write() メソッドを呼び出す

インタフェース

putchar()

void mwx::stream::putchar(char c)

// example
Serial.putchar('A');
// result -> A

1バイト出力します。

size_t print(T val, int base = DEC) // T: 整数型
size_t print(double val, int place = 2)
size_t print(const char*str)
size_t print(std::initializer_list<int>)

// example
Serial.print("the value is ");
Serial.print(123, DEC);
Serial.println(".");
// result -> the value is 123.

Serial.print(123.456, 1);
// result -> 123.5

Serial.print({ 0x12, 0x34, 0xab, 0xcd });
// will output 4byte of 0x12 0x34 0xab 0xcd in binary.

各種整形出力を行います。

パラメータ解説
val整形出力したい数値型
base出力形式BIN 二進数 / OCT 8進数 / DEC 10進数 / HEX 16進数
place小数点以下の桁数
戻り値 size_t書き出したバイト数

printfmt()

size_t printfmt(const char* format, ...);

// example
Serial.printfmt("the value is %d.", 123);
// result -> the value is 123.

printf 形式での出力を行います。

TWESDK/TWENET/current/src/printf/README.md 参照

operator <<

// examples
Serial << "this value is" // const char*
       << int(123)
       << '.';
       << mwx::crlf;
// result -> this value is 123.

Serial << fromat("this value is %d.", 123) << twe::crlf;
// result -> this value is 123.

Serial << mwx::flush; // flush here

Serial << bigendian(0x1234abcd);
// will output 4byte of 0x12 0x34 0xab 0xcd in binary.

Serial << int(0x30) // output 0x30=48, "48"
       << '/'
       << uint8_t(0x31); // output '1', not "48"
// result -> 48/1

smplbuf<char,16> buf = { 0x12, 0x34, 0xab, 0xcd };
Serail << but.to_stream();
// will output 4byte of 0x12 0x34 0xab 0xcd in binary.

Seiral << make_pair(buf.begin(), buf.end());
// will output 4byte of 0x12 0x34 0xab 0xcd in binary.

Serial << bytelist({ 0x12, 0x34, 0xab, 0xcd });
// will output 4byte of 0x12 0x34 0xab 0xcd in binary.
引数型解説
char1バイト出力 (数値としてフォーマットはしない)
int整数出力 (printf の “%d”)
double数値出力 (printf の “%.2f”)
uint8_t1バイト出力する(char型と同様)
uint16_t2バイト出力する(ビッグエンディアン順)
uint32_t4バイト出力する(ビッグエンディアン順)
const char*``uint8_t*``const char[S]終端文字までを出力します。出力には終端文字は含まれません。(Sは固定配列のサイズ指定)
uint8_t[S]配列サイズSバイト分をそのまま出力します。(Sは固定配列のサイズ指定)
format()printf 形式での出力
mwx::crlf改行 CRLF の出力
mwx::flush出力のフラッシュ
bigendian()数値型をビッグエンディアン順で出力する。(右辺値)
std::pair<T*, T*>バイト型の begin(), end() ポインタを格納したペア。make_pair により生成できる。Tuint8_t 型を想定する。(右辺値)
bytelist()std::initializer_list を用いるバイト列の出力
smplbuf<uint8_t,AL>&uint8_t型の配列クラスの内容を出力する。ALCメモリ確保手段
smplbuf<uint8_t, AL>::to_stream()smplbuf&#x3C;T> のデータを出力する
Tuint8_t型、ALメモリ確保手段

set_timeout(), get_error_status(), clear_error_status()

uint8_t get_error_status()
void clear_error_status()
void set_timeout(uint8_t centisec)

// example
Serial.set_timeout(100); // 1000msのタイムアウトを設定
uint8_t c;
Serial >> c;

>>演算子を用いた入力タイムアウトとエラーを管理します。

set_timeout() によりタイムアウト時間を指定し、>>演算子により入力処理を行います。所定時間内までに入力が得られない場合は get_error_status() によりエラー値を読み出せます。clear_error_status()によりエラー状況をクリアします。

引数型解説
centisec1/10秒単位でタイムアウト時間を設定します。0xffを指定した場合は、タイムアウトを無効とします。

エラー値

意味
0エラーなし
1エラー状況

operator >>

inline D& operator >> (uint8_t& v)
inline D& operator >> (char_t& v)
template <int S> inline D& operator >> (uint8_t(&v)[S])
inline D& operator >> (uint16_t& v)
inline D& operator >> (uint32_t& v)
inline D& operator >> (mwx::null_stream&& p)

//// 例
uint8_t c;

the_twelite.stop_watchdog(); // ウォッチドッグの停止
Serial.set_timeout(0xFF); // タイムアウト無し

// 1バイト読み出す
Serial >> c;
Serial << crlf << "char #1: [" << c << ']';

// 読み捨てる
Serial >> null_stream(3); // 3バイト分読み捨てる
Serial << crlf << "char #2-4: skipped";

// 4バイト分読み出す (uint8_t 型固定長配列限定)
uint8_t buff[4];
Serial >> buff;
Serial << crlf << "char #5-8: [" << buff << "]";

入力処理を行います。

以下に読み出し格納できる型を列挙します。

引数型解説
uint8_t, char_t1バイト入力
uint16_t2バイト入力(ビッグエンディアン順)
uint32_t4バイト入力(ビッグエンディアン順)
uint8_t[S]Sバイト分入力(Sは固定配列のサイズ指定)
null_stream(int n)nバイト読み捨てる

10.1 - mwx::mwx_format

printf の書式入力
mwx::stream の « 演算子に対してフォーマット書式を書き出すヘルパークラスです。

ライブラリ内では Using format=mwx::mwx_format; として別名定義しています。

Serial << format("formatted print: %.2f", (double)3123 / 100.) << mwx::crlf;

// formatted print: 31.23[改行]
  • コンストラクタで受け取った引数リストを、パラメータパックの展開機能を用いてクラス内部変数に格納する
  • operator << が呼び出された時点で、fctprintf() を呼び出し、ストリームにデータを書き出す

コンストラクタ

format(const char *fmt, ...)

コンストラクタでは、書式のポインタとパラメータを保存します。続く <<演算子による呼び出しでフォーマットを解釈して出力処理を行います。

パラメータ解説
fmtフォーマット書式。TWESDK/TWENET/current/src/printf/README.md 参照
...フォーマット書式に応じたパラメータ。
※ 最大数は4で、5つ以上のパラメータではコンパイルエラーとなる。※ 書式との整合性はチェックしないため、不整合な入力に対しては安全ではない。

10.2 - mwx::bigendian

ビッグエンディアン順のデータ出力
mwx::stream<< 演算子に対して数値型をビッグエンディアンのバイト列で出力するヘルパークラスです。
Serial << mwx::bigendian(0x1234abcdUL);

// output binary -> 0x12 0x34 0xab 0xcd

コンストラクタ

template <typename T>
bigendian::bigendian(T v)
パラメータ解説
vuint16_t または uint32_t の型の値

10.3 - mwx::crlf

改行コード出力
mwx::stream<< 演算子に対して改行コード (CR LF) を出力するためのヘルパークラスのインスタンスです。
Serial << "hello world!" << mwx::crlf;

10.4 - mwx::flush

出力バッファ強制出力
mwx::stream の出力バッファをフラッシュします。

flush() メソッドを呼び出すヘルパークラスへのインスタンスです。

for (int i = 0; i < 127; ++i) {
    Serial << "hello world! (" << i << ")" << twe::endl << twe::flush;
}
  • シリアルポートの場合は出力完了までポーリング待ちを行う
  • mwx::simpbuf バッファの場合は 0x00 を末尾に出力する(サイズは変更しない)

10.5 - stream_helper

ヘルパーオブジェクト
stream_helperは、mwx::streamインタフェースを付与するヘルパーオブジェクトです。データクラスを参照するヘルパーオブジェクトを生成し、ヘルパーオブジェクト経由でデータの入出力を行います。

以下にはsmplbufの配列bからヘルパーオブジェクトbsを生成しmwx::stream::operator <<()演算子によるデータ入力を行っています。

smplbuf_u8<32> b;
auto&& bs = b.get_stream_helper(); // ヘルパーオブジェクト

// データ列の生成
uint8_t FOURCHARS[]={'A', 'B', 'C', 'D'};
bs << FOURCHARS;
bs << ';';
bs << uint32_t(0x30313233); // "0123"
bs << format(";%d", 99);

Serial << b << crlf; // Serialへの出力は smplbuf_u8<32> クラス経由で

//結果: ABCD;0123;99

概要

stream_helper はデータ配列をストリームに見立てて振舞います。

内部にはデータ配列中の読み書き位置を保持しています。次のようにふるまいます。

  • 読み出しまたは書き込みをすると次の読み書き位置に移動します。
  • 最期のデータを読み出した後、またはデータを末尾に追記した後には、読み書き位置は終端となります。
  • 読み書き位置が終端の場合、
    • available()falseになります。
    • 読み出しは出来ません。
    • 書き込みは書き込み可能範囲であれば追記します。

stream_helperの生成

stream_helper は、データクラス (smplbuf, EEPROM) のメンバー関数より生成します。

auto&& obj_helper = obj.get_stream_helper()
// obj はデータクラスのオブジェクト、obj_helperの型は長くなるのでauto&&で受けています。

メソッド

rewind()

void rewind()

読み書き位置を先頭に移動します。

seek()

int seek(int offset, int whence = MWX_SEEK_SET)

読み書き位置を設定します。

whence設定位置
MWX_SEEK_SET先頭位置から設定します。offset0を指定するとrewind()と同じ意味になります。
MWX_SEEK_CUR現在位置を基準にoffset分移動しまします。
MWX_SEEK_END終端位置にします。offset0にすると終端に設定します。-1を設定すると最後の文字に移動します。

tell()

int tell()

読み書き位置を返します。終端位置の場合は-1を返します。

available()

int available()

読み書き位置が終端であれば0を返します。終端でなければそれ以外の値を返します。

11 - SM_SIMPLE ステートマシン

状態管理
SM_SIMPLEは、サンプルコード中の状態遷移、タイムアウト待ち、送信完了などの処理待ちを行うために用意しています。

SM_SIMPLEの基本的なコードを示します。

##include <SM_SIMPLE>

enum class STATE : uint8_t {
	INIT = 0,
	SENSOR,
	TX,
	TX_WAIT_COMP,
	GO_SLEEP
};

SM_SIMPLE<STATE> step;

begin() {
  ...
  step.init(); //初期化
}

loop() {
  do {
    switch(step.state()) {
    case STATE::INIT:
      ...
      step.next(STATE::SENSOR);
    break;

    case STATE::SENSOR:
      ...
      step.next(STATE::TX);
    break;

    case STATE::TX:
      if (/*送信要求成功*/) {
        step.set_timeout(100); // タイムアウトの設定
        step.clear_flag(); //処理待ち

        step.next(STATE::TX_WAIT_COMP);
      }
    break;

    case STATE::TX_WAIT_COMP:
      if (step.is_timeout()) the_twelite.reset_system(); // タイムアウト
      if (step.is_flag_ready()) sleepNow(); // flagがセットされた
    break;

    ...
    }
  } while(step.b_more_loop());
}

void on_tx_comp(mwx::packet_ev_tx& ev, bool_t &b_handled) {
	step.set_flag(ev.bStatus);
}

void sleepNow() {
	step.on_sleep(false); // reset state machine.
  the_twelite.sleep(10000); // 10sec
}

解説

SM_SIMPLEを利用するには状態一覧としてのenum class定義が必要です。上記ではSTATEとして定義しています。このステージをパラメータとしてSM_SIMPLE<STATE> step;のようにクラスオブエクトを生成します。生成したクラスオブジェクトは.setup()により初期化しておきます。

enum class STATE : uint8_t {
	INIT = 0,
	SENSOR,
	TX,
	TX_WAIT_COMP,
	GO_SLEEP
};

SM_SIMPLE<STATE> step;

void setup() {
  step.init();
}

SM_SIMPLEの初期状態は値が0で、上記の例ではSTATE::INITが対応します。現在の状態を取得するには.state()を用、上記例のように_do while_文中の_switch_節の判定式に用います。

loop() {
  do {
    switch(step.state()) {
    case STATE::INIT: // 値0の状態
    ...

状態の遷移には.next()を呼び出します。状態が変更された場合、b_more_loop()trueになり_do while_節のループがもう一度実行されます。例ではSTATE::SENSOR状態から.next(STATE::TX)を呼び出すことで、ループがもう一度実行されcase STATE::TX:節も実行されることになります。状態を変更しない場合は_do while_ループを脱出しloop()を一旦終了します。次のloop()の呼び出しまで一旦待ちます。

  do {
    switch(step.state()) {
    ...
    case STATE::SENSOR:
      ...
      step.next(STATE::TX); // (1)状態遷移
    break;

    case STATE::TX: // (3) 2回めのループで呼び出される
      if (/*送信要求成功*/) {
      ...
    }
  } while (b_more_loop()); // (2) ループ継続判定 true

送信完了などの処理待ちをしたい場合は.clear_flag()を呼び出し、別のコールバック関数などで.set_flag(uint32_t)により処理完了を知らせます。ここで指定したuint32_t型のパラメータをは.get_flag_value()から読み出せます。

またタイムアウトの処理を行いたい場合は.set_timeout(uint32_t)を呼び出した時刻を記録し、.is_timeout()によりタイムアウト時間が経過したかを調べることができます。

    case STATE::TX:
      if (/*送信要求成功*/) {
        step.set_timeout(100); // タイムアウトの設定
        step.clear_flag(); //処理待ち

        step.next(STATE::TX_WAIT_COMP);
      }
    break;

    case STATE::TX_WAIT_COMP:
      if (step.is_timeout()) ...; // タイムアウト
      if (step.is_flag_ready()) ...; // flagがセットされた
    break;
...

// 送信完了イベント
void on_tx_comp(mwx::packet_ev_tx& ev, bool_t &b_handled) {
	step.set_flag(ev.bStatus); // flag を設定する
}

スリープからの復帰で再びSM_SIMPLEを利用することになりますが、スリープ前に必ず.on_sleep(bool)を呼び出すようにします。パラメータにfalseを入れると復帰後に0状態から開始し、trueを入れるとスリープ直前の状態から再開します。

void sleepNow() {
	step.on_sleep(false); // reset state machine.
  the_twelite.sleep(10000); // 10sec
}

ソースコード

以下にSM_SIMPLEのソースコードを示します。

// very simple class to control state used in loop().
template <typename STATE>
class SM_SIMPLE {
	uint32_t _u32_flag_value;  // optional data when flag is set.
	uint32_t _ms_start;		// system time when start waiting.
	uint32_t _ms_timeout;	// timeout duration

	STATE _step;			  // current state
	STATE _step_prev;		// previous state
	bool_t _b_flag; 		// flag control.
public:
	// init
	void setup() { memset(this, 0, sizeof(SM_SIMPLE)); }
	// call befoer sleeping (save state machine status)
	void on_sleep(bool b_save_state = false) {
		STATE save = _step;
		setup();
		if(b_save_state) _step = _step_prev = save;
	}

	// state control
	void next(STATE next) { _step = next; } // set next state
	STATE state() { return _step; } // state number
	bool b_more_loop() { // if state is changed during the loop, set true
		if (_step != _step_prev) { _step_prev = _step; return true; }
		else return false;
	}

	// timeout control
	void set_timeout(uint32_t timeout) {
		_ms_start = millis();
		_ms_timeout = timeout;
	}
	bool is_timeout() { return (millis() - _ms_start) >= _ms_timeout; }

	// flag control
	void clear_flag() { _b_flag = false; _u32_flag_value = 0; }
	void set_flag(uint32_t u32_flag_value = 0) {
		_b_flag = true;
		_u32_flag_value = u32_flag_value; }
	uint32_t get_flag_value() { return _u32_flag_value; }
	bool is_flag_ready() { return _b_flag; }
};
  • バージョンによって内容が変化する場合があり。
  • 本体は MWX ライブラリソースフォルダのSM_SIMPLE.hppに格納されます。