セクションの複数ページをまとめています。 印刷またはPDF形式で保存...
クラス
- 1: MWX_APIRET
- 2: alloc
- 3: axis_xyzt
- 4: packet_rx
- 5: packet_tx
- 6: serparser
- 7: pktparser
- 7.1: E_PKT
- 7.2: identify_packet_type()
- 7.3: TwePacket
- 7.3.1: TwePacketTwelite
- 7.3.2: TwePacketIO
- 7.3.3: TwePacketUART
- 7.3.4: TwePacketPAL
- 8: smplbuf
- 8.1: get_stream_helper()
- 8.2: smplbuf_strm_u8
- 9: smplque
- 10: 入出力ストリーム
- 10.1: mwx::mwx_format
- 10.2: mwx::bigendian
- 10.3: mwx::crlf
- 10.4: mwx::flush
- 10.5: stream_helper
- 11: SM_SIMPLE ステートマシン
1 - MWX_APIRET
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_attach
やalloc_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
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
tsRxDataApp
構造体のラッパークラスです。このクラスオブジェクトは、ビヘイビアのコールバック関数またはon_rx_packets()
により取得できます。
packet_rx
では、特にパケットのデータペイロードをsmplbuf
コンテナで取り扱えるようにし、expand_bytes()
などのユーティリティ関数によりペイロードの解釈記述を簡素化しています。
<NWK_SIMPLE>
で必要とされるものを中心にメソッド等のインタフェースを実装しています。メソッド
get_payload()
smplbuf_u8_attach& get_payload()
パケットのデータペイロードを取得する。
<NWK_SIMPLE>
を用いた場合は、先頭に<NWK_SIMPLE>
用のヘッダデータがあります。戻りとして参照されるコンテナは、このヘッダ部分を除いた部分配列になります。ヘッダ部分まで参照したいときはget_psRxDataApp()
によりtsRxDataApp
構造体を参照してください。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)を得る。
LQIとは電波通信品質を示す値です。0から255までの数値で表されます。
ちなみに、いくつかの段階で評価する場合は、50未満(悪い -80dbm 未満)、50~100(やや悪い)、100~150(良好)、150以上(アンテナの近傍)といった区分けも可能です。これらは目安である点にご留意ください。
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
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
オブジェクトは、まず、()
内の条件判定にてtrue
かfalse
を返します。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)
パケットを送信するまでの遅延と再送間隔を設定します。u16DelayMin
とu16DelayMax
の2つの値をミリ秒[ms]で指定します。送信要求をしてからこの間のどこかのタイミングで送信を開始します。再送間隔をu16RetryDur
の値[ms]で指定します。再送間隔は一定です。
内部処理の都合で指定通りのタイミングで送信処理が始まらない場合もあります。また、IEEE802.15.4の処理でもパケット創出までの時間ブレが発生します。これらのタイミングのブレは、多くのシステムではパケットの衝突回避を行う上で有効な手立てとなります。
厳格なタイミングでのパケット送信は、IEEE802.15.4の規格の性質上、例外的な使用方法とお考え下さい。
<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
メモリバッファ取り扱い方法(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) コロンを指定します。 | |
データ部 | N | 2N | 元データの各バイトをアスキー文字列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
定義
====== | 元データのバイト数 | 形式におけるバイト数 | 解説 |
---|---|---|---|
ヘッダ | 2 | 0xA5 0x5A を指定します。 | |
データ長 | 2 | データ長はビッグエンディアン形式の2バイトで、MSB (0x8000) を設定した上、データ部の長さを指定します。 例えばデータ部の長さが 8 バイトなら 0x80 0x08 を指定します。 | |
データ部 | N | N | 元データを指定します。 |
チェックサム | 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_heap | begin() メソッドのパラメータで指定したサイズをヒープに確保する |
メモリ確保クラスに応じた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
を戻します。
parse()
で読み出し完了になったとき、次のparse()
を実行すると読み出し中のステータスに戻ります。例
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
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
を指定します。
p
とe
はバイト列の先頭と終端の次を指定します。
戻り値はE_PKT
型です。エラーの場合はE_PKT::PKT_ERROR
が戻ります。
user<T>
template
T& use()
解釈したバイト列に対応するパケット型に対応するオブジェクトの参照を返します。事前にparse<T>を実行しエラーがなかった場合に呼び出すせます。
T
はparse<T>
で実行した型と同じもの、または基本的な情報のみ取得できるTwePacket
を指定します。
7.1 - E_PKT
以下のパケットに対応します。
名前 | 解説 |
---|---|
PKT_ERROR | パケット解釈前やパケット種別が特定できないなど、TwePacketには意味のあるデータが格納されていない |
PKT_TWELITE | 標準アプリ App_Twelite の 0x81 コマンドを解釈したもの |
PKT_PAL | TWELITE 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;
};
pktparser
型として配列等に格納するような場合に、アドレス情報などを最小限の情報を取得したい場合に使用します。7.3.1 - TwePacketTwelite
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
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
TwePacketAppUart
クラスは、App_UARTの拡張書式を親機・中継機アプリApp_Wingsで受信したときの形式です。
class TwePacketAppUART : public TwePacket, public DataAppUART
パケットデータ内の諸情報はparse<TwePacketUART>()
実行後にDataAppUART
に格納されます。
parse<TwePacketUART>()
ではE_PKT::PKT_ERROR
を戻します。内容を確認するには元のバイト列を直接参照してください。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
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()
を呼び出して利用してください。
setup()
を推奨)に初期化関数init_local()
,attach()
,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種類あります。
get_stream_helper()
により生成されるヘルパーオブジェクトを利用する。mwx::stream
を継承したsmplbufクラスを利用する。
8.1 - get_stream_helper()
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
// 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
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()
を呼び出して利用してください。
setup()
を推奨)に初期化関数init_local()
,attach()
,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>;
のように定義し、上位クラスからも下位クラスのメソッドを参照します。
- CRTP では下位クラスは
- 本クラスでは
print
メソッド、<<
演算子などの共通処理の定義を行い、下位クラスで実装したwrite()
メソッドなどを呼び出すことで、仮想関数を用いるのと近い実装を行っています。
インタフェース(下位クラスで実装)
下位クラスでは、以下に列挙する関数を実装します。
available()
int available()
// example
while(Serial.available()) {
int c = Serial.read();
// ... any
}
入力が存在する場合は 1、存在しない場合は 0 を返します。
パラメータ | 解説 |
---|---|
戻り値 int | 0: データなし 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バイト出力します。
print()
, println()
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.
引数型 | 解説 |
---|---|
char | 1バイト出力 (数値としてフォーマットはしない) |
int | 整数出力 (printf の “%d”) |
double | 数値出力 (printf の “%.2f”) |
uint8_t | 1バイト出力する(char型と同様) |
uint16_t | 2バイト出力する(ビッグエンディアン順) |
uint32_t | 4バイト出力する(ビッグエンディアン順) |
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 により生成できる。T は uint8_t 型を想定する。(右辺値) |
bytelist() | std::initializer_list を用いるバイト列の出力 |
smplbuf<uint8_t,AL>& | uint8_t 型の配列クラスの内容を出力する。ALC はメモリ確保手段。 |
smplbuf<uint8_t, AL>::to_stream() | smplbuf<T> のデータを出力するT は uint8_t 型、AL はメモリ確保手段。 |
uint8_t, uint16_t, uint32_t
型にキャストします。また文字列として数値出力する場合は明示的にint
形にキャストするようにしてください。uint8_t[S]
型を用いるようにしてください。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()
によりエラー状況をクリアします。
引数型 | 解説 |
---|---|
centisec | 1/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 << "]";
入力処理を行います。
setup()
内では実行できません。- ポーリング待ちを行うため、タイムアウトの時間設定(タイムアウト無しなど)によっては、ウォッチドッグタイマーが発動してリセットする場合があります。
通常はloop()
中で以下のような読み出しを行います。
void loop() {
uint8_t c;
while(Serial.available()) {
Serial >> c;
// または c = Serial.read();
switch(c) { ... } // cの値によって処理を分岐する
}
}
以下に読み出し格納できる型を列挙します。
引数型 | 解説 |
---|---|
uint8_t, char_t | 1バイト入力 |
uint16_t | 2バイト入力(ビッグエンディアン順) |
uint32_t | 4バイト入力(ビッグエンディアン順) |
uint8_t[S] | S バイト分入力(S は固定配列のサイズ指定) |
null_stream(int n) | n バイト読み捨てる |
10.1 - mwx::mwx_format
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つ以上のパラメータではコンパイルエラーとなる。※ 書式との整合性はチェックしないため、不整合な入力に対しては安全ではない。 |
fmt
は本オブジェクトが破棄されるまで、アクセス可能であることが必要です。10.2 - mwx::bigendian
mwx::stream
の <<
演算子に対して数値型をビッグエンディアンのバイト列で出力するヘルパークラスです。
Serial << mwx::bigendian(0x1234abcdUL);
// output binary -> 0x12 0x34 0xab 0xcd
コンストラクタ
template <typename T>
bigendian::bigendian(T v)
パラメータ | 解説 |
---|---|
v | uint16_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 | 先頭位置から設定します。offset に0 を指定するとrewind() と同じ意味になります。 |
MWX_SEEK_CUR | 現在位置を基準にoffset 分移動しまします。 |
MWX_SEEK_END | 終端位置にします。offset は0 にすると終端に設定します。-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
に格納されます。