glancer_con

App_Wings(または各アプリのUART出力)を解釈して、各パケットメッセージの概要を表示します。

glancerは「glance(一目する)人」の意味の英単語です。

ファイル構成

プロジェクトフォルダ

examples/glancer      --- Makefile
msc/MSC_glancer_con   --- VC++ Project 定義

プロジェクトフォルダのソースコードはglancer.cppのみです。残りの必要なコードはsrc/..を参照します。

他のプロジェクトなどに移植する場合

MWM5ライブラリ全てのファイルは必要ありません。以下に必要なファイルを列挙します。

twe_fmt.cpp
twe_sercmd.cpp
twe_sercmd_ascii.cpp
twe_utils_crc8.cpp

twe_common.hpp
twe_fmt.hpp
twe_sercmd.hpp
twe_sercmd_ascii.hpp
twe_stream.hpp
twe_utils.hpp
twe_utils_crc8.hpp
twe_utils_fixedque.hpp
twe_utils_simplebuffer.hpp

上記で必要なファイル等は本ドキュメントと相違ある場合もあります。問題があればビルド時にエラーが表示されますので、エラーファイルに記述される"~not found"といったメッセージを頼りに足りないファイルをコピーしてください。

ビルド

ビルドに必要な定義

定義

内容

TWE_STDINOUT_ONLY

必須。標準入出力のみでビルドしたい場合に必要な定義です。

TWE_HAS_MILLIS

システム時間[ms]を得るmillis()を実装した場合は定義します。定義するとパケット情報にタイムスタンプ[ms]情報が含まれます。

millis() の実装には、システム時間を取得するためのOS依存のシステムコールが必要です。

ビルド方法(VC++, Windows)

examples/glancer_con というプロジェクト定義がありますので、こちらをビルドしてください。

ビルド方法 (Makefile, gcc)

make (GNU) と g++ (gcc より) が必要です。事前にインストールを済ませてください。

Makefileを確認してください。以下の定義は必要に応じて書き換えます。

定義名

CXX

g++-9 など実行名が違う場合に書き換えます。

※ Makefile 中で動作環境を判定している部分があり、この部分に定義が含まれます。

DEFINES

必要に応じて。 -D??? の定義をコンパイラに与えます。

CFLAGS

必要に応じて。コンパイルオプションをコンパイラに与えます。

makeコマンドを実行します。

millis()の実装

#if defined(_MSC_VER) || defined(__MINGW32__)
#include "windows.h"
# pragma comment(lib, "secur32.lib")
# pragma comment(lib, "winmm.lib")
# pragma comment(lib, "dmoguids.lib")
# pragma comment(lib, "wmcodecdspuuid.lib")
# pragma comment(lib, "msdmo.lib")
# pragma comment(lib, "Strmiids.lib")
#elif defined(__APPLE__) || defined(__linux)
#include <sys/time.h>
#endif

uint32_t millis() {
#if defined(_MSC_VER) || defined(__MINGW32__)
	return (uint32_t)timeGetTime();
#elif defined(__APPLE__) || defined(__linux)
	timeval time;
	gettimeofday(&time, NULL);
	long ms = (time.tv_sec * 1000) + (time.tv_usec / 1000);
	return (uint32_t)ms;
#else
	# warning "no millis() implementation."
	return 0;
#endif
}

実行例

:78811501C98201015A000391000C2E00810301FFFFFFFFFB
PKT:Typ=1:Lq=201:Ad=0x8201015a(0x78):Vmv=3118:Tms=2656:App_Twelite:DI1..4=LHHH:AI1..4=(0028,----,----,----)
:78A0028201015AFFFFFFFFA8000700112233AABBCCC6
PKT:Typ=4:Lq=168:Ad=0x8201015a(0x78):Vmv=0000:Tms=5430:App_UARTMSG=0x00112233aabbcc
:8000000084811F810EFF6D04808205113008020AEB11300102035A0501000209E3010200020E3A02030004000001BE6C00
PKT:Typ=2:Lq=132:Ad=0x810eff6d(0x04):Vmv=2795:Tms=3904:PAL_AMB:TEMP=25.31:HUMD=36:LUMI=446

標準入力(通常の1バイト入力を用いているため改行が入力されるまでは入力文字列は評価されません)にアスキー形式のUARTメッセージを入力します。出力はPKT...となります。

先頭の項目について解説します。

項目

内容

Typ=

Lq=

LQI値が格納されます。

Ad=

32bitの送信元アドレスと8bitの送信元論理アドレスが格納されます。

Vmv=

電源電圧です。

※ メッセージにデータがない場合は 0000 となります。

Tms=

メッセージを受信したシステム時間が格納されます。

millis()を実装しておく必要があります。

ソースコードの構造

解説 Parser についても参照ください。

ヘッダ部

#include <iostream>
#include <iomanip> 
#include <string>

#include "twe_common.hpp"
#include "twe_sercmd_ascii.hpp"
#include "twe_fmt.hpp"

using namespace TWE;
using namespace TWEUTILS;
using namespace TWESERCMD;
using namespace TWEFMT;

AsciiParser parse_ascii(256);

必要な標準ライブラリヘッダとMWM5ライブラリヘッダを読み込みます。

MWM5ライブラリでは細かく名前空間が使用されていますが、利用時には煩雑ですのですべてusing namespaceにより省略可能とします。

AsciiParserのクラスインスタンスを生成しておきます。

メインループ

int c;
while ((c = std::cin.get()) >= 0) {
	if (c == '\r' || c == '\n') {
		parse_ascii << char_t(0x0d); // always 0x0d (this parser does not check the following 0x0a)
	}
	else {
		parse_ascii << char_t(c);
	}

	if (parse_ascii) {
		std::cout << "PKT";
		auto&& payl = parse_ascii.get_payload(); // payload data array
		auto&& pkt = newTwePacket(payl);         // packet object (contains minimum information)
		auto&& typ = identify_packet_type(pkt);  // packet type
		std::cout << ":Typ=" << int(typ);

		if (typ != E_PKT::PKT_ERROR) {
		   // ... display common packet info 
		}
		
		// display each packet information
		switch (typ) {
			case E_PKT::PKT_PAL: print_pal(pkt); break;
			case E_PKT::PKT_ACT_STD: print_act(pkt); break;
			case E_PKT::PKT_TWELITE: print_app_twelite(pkt); break;
			case E_PKT::PKT_APPIO: print_app_io(pkt); break;
			case E_PKT::PKT_APPUART: print_app_uart(pkt); break;
			case E_PKT::PKT_APPTAG: print_app_tag(pkt); break;
			default: print_unknown(payl); // e.g. UART message of App_Twelite, App_UART(simple format), or corrupted.
		}
  }
}

このサンプルでは標準入力から1バイトずつ入力(std::cin::get())して、これをASCIIパーサーに投入します。

標準入力ではシステムによって改行コードが異なりますが、ここでは'r'または'\n'のいずれが来ても'\r'を投入するようにしています。本パーサーは末尾の'\r'のみを評価するためです。

	if (parse_ascii) {

投入後書式が完了した場合はparse_asciitrueを戻します。ここでデータを解釈します。

auto&& payl = parse_ascii.get_payload();
auto&& pkt = newTwePacket(payl);

ASCIIパーサーにより得られたバイト列は.get_payload()メソッドにより配列SmplBuf_Byte型として取り出すことが出来ます。

このサンプルでは得られたバイト列を解釈して、どの種別のUARTメッセージなのか、何が格納されているのかを取り出せるようにします。

バイト列を解釈する手続きはnewTwePacket()にバイト列paylを与えます。戻り値としてクラスオブジェクトpktを生成します。このオブジェクトはspTwePacket型の(メモリ管理を行う)スマートオブジェクトで、TwePacket型のオブジェクトを格納します。

auto&& typ = identify_packet_type(pkt);  // packet type

実際にはpktは種別に応じたTwePacket型の派生クラスのインスタンスが格納されています。この派生クラスの種別を確認するのがidentify_pkt_type()で、パケット種別としてE_PKT型の値を返します。

パケット種別が得られたら、パケット種別ごとの表示関数print_???(spTwePacket)を呼び出しています。

ここから先はパケット種別ごとに手続きが違います。以下にApp_Tweliteの解釈例、App_PALの解釈例を示します。

void print_app_twelite(spTwePacket pkt) {
	auto&& atw = refTwePacketTwelite(pkt);
	std::cout << ":DI1..4="
			<< char(atw.DI1 ? 'L' : 'H') 
			<< char(atw.DI2 ? 'L' : 'H')
			<< char(atw.DI3 ? 'L' : 'H')
			<< char(atw.DI4 ? 'L' : 'H');
	...

App_Twelite の UARTメッセージの解釈を行います。まずspTwePacket型のままでは、App_Twelite特有のデータにアクセスできません。

まずrefTwePacketTwelite()を呼びます。戻り値はTwePacketTweliteの参照型です。App_TweliteのUARTメッセージを解釈した特有のデータが含まれます。例えば.DI1はDI1のHigh/Low状態を示します。

void print_pal(spTwePacket pkt) {
	auto&& pal = refTwePacketPal(pkt);

	if (pal.is_PalEvent()) {
		std::cout << "PAL_EVENT:ID=" 
				<< int(pal.get_PalEvent().u8event_id);
	} else switch(pal.u8palpcb) {
		case E_PAL_PCB::MAG:
		{
			// generate pal board specific data structure.
			PalMag mag = pal.get_PalMag();

			std::cout << "PAL_MAG";
			std::cout << ":STAT=" << int(mag.u8MagStat);
			std::cout << "(";
	...

TWELITE PALのUARTメッセージを解釈します。TWELITE PALは接続される基板によって異なるUARTメッセージが出力されます。このためデータ取得も一旦PALとして解釈してから、さらに個別のPAL基板のデータを取り出します。

auto&& pal = refTwePacketPal(pkt);

最初にPAL共通データをrefTwePacketPal()により取り出します。

if (pal.is_PalEvent()) {

最初にイベントパケットかどうかの判定を行います。イベントパケットは例えば通知(NOTICE)PALの加速度センサーが静止状態から加速度を検出した(タップ)といった現象をイベント番号として伝えるものです。イベントパケットにも様々なデータが付加されますが、通常はイベント番号を参照します。

switch(pal.u8palpcb) {

イベントパケットでなかった場合は、いずれかのPAL基板の標準的なパケットデータが含まれます。どの基板であるかを判定するには.u8palpcbを参照します。

case E_PAL_PCB::MAG:
{
			PalMag mag = pal.get_PalMag();
			std::cout << ":STAT=" << int(mag.u8MagStat);
			...

PAL基板の種別がわかったら、PAL基板の種別に応じた手続き(.get_PalMag()など)により、PAL基板特有のデータが含まれるクラスオブジェクトを得ます。

上記の例では開閉センサーPALの磁気センサーの状態を得ています。

最終更新