セクションの複数ページをまとめています。 印刷またはPDF形式で保存...
TWELITE WINGS API / MWings
- 1: TWELITE Wings API / MWings for Python
- 2: TWELITE Wings API / MWings for 32-bit Arduinos
- 2.1: TWELITE Wings API / MWings for 32-bit Arduinos
- 2.1.1: TWELITE SPOT に使用する
- 2.1.2: Arduino UNO R4 に使用する
- 2.1.3: パケットパーサの拡張
1 - TWELITE Wings API / MWings for Python
1.1 - TWELITE Wings API / MWings for Python
ライブラリの概要
TWELITE Wings API (以降 MWings) は、Python スクリプトから TWELITE を扱うためのライブラリです。
機能
ホストへ接続された TWELITE の親機を通じて、TWELITE の子機と通信できます。
- 受信データを解釈して、辞書や JSON のほか pandas データフレームへ変換※
- 辞書から生成したコマンドを親機へ送信
※ Lite版は pandas に非対応
Raspberry Pi には、通常版ではなくLite版を推奨します
- モジュール名は
mwings
ではなく、mwingslite
です - pandas や numpy, pyarrow 等への依存関係がありません
- Raspberry Pi では、これらをPyPIから導入できないことがあります
- なお pandas がないとき、データフレームを出力する関数
to_df()
は例外を発します
- 通常版と同じく、辞書やJSON文字列の出力には対応しています
- 要求する Python のバージョンを 3.11 以降へ落としています(通常版は 3.12 以降)
用途例
例えば、次のようなシステムを実現できます。
- MONOSTICK で受信した温湿度データを JSON としてクラウドサーバへ送信
- MONOSTICK で受信した加速度データを CSV または Excel ファイルへ記録※
- PC から MONOSTICK を通じて TWELITE DIP へ接続された LED を制御
※ Lite版は、直接 CSV や Excel ファイルを出力できません
特徴
モダン Python の モダン Python による モダン Python のためのモジュールです。
※ 例外あり。詳細は後述
インストール
PyPI から入手できます。
pip の場合
pip install mwings
poetry の場合
poetry add mwings
最も簡単なサンプルスクリプト
わずか6行で超簡単!標準アプリ(App_Twelite)の受信データを JSON 形式で出力できます。
import mwings as mw
twelite = mw.Twelite(mw.utils.ask_user_for_port())
@twelite.on(mw.common.PacketType.APP_TWELITE)
def on_app_twelite(packet):
print(packet.to_json())
twelite.start()
Lite版の場合
import mwings as mw
をimport mwingslite as mw
とします。環境整備と動作確認
用意するもの
- PC
- MONOSTICK(親機・中継機アプリ/デフォルト設定)
- TWELITE DIP(超簡単!標準アプリ/デフォルト設定)
- スイッチなどのペリフェラルを接続しておく(例:DI1 ポートと GND 間にタクトスイッチを接続)
環境整備
以下は一例です。Python 3.12 以降(Lite版は3.11以降)が使える環境であれば問題ありません。
好みの環境があれば、読み飛ばしてください。
外部ツールの使用において、当社は一切の責任を負いません。
また、外部ツールに関する質問はご遠慮ください。
pyenv の導入
処理系のバージョンを管理するために、pyenv を導入します。
Linux
curl https://pyenv.run | bash
事前に開発ツールを導入しておかなくてはならない場合があります
Debian系
sudo apt update; sudo apt install build-essential libssl-dev zlib1g-dev libbz2-dev libreadline-dev libsqlite3-dev curl git libncursesw5-dev xz-utils tk-dev libxml2-dev libxmlsec1-dev libffi-dev liblzma-dev
Fedora系
yum install gcc make patch zlib-devel bzip2 bzip2-devel readline-devel sqlite sqlite-devel openssl-devel tk-devel libffi-devel xz-devel
macOS
brew update
brew install pyenv
必要に応じて Homebrew を導入してください
/bin/bash -c "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/HEAD/install.sh)"
事前に開発ツールを導入しておかなくてはならない場合があります
Windows
Windows 向けの pyenv はありません。代わりに pyenv-win を使用します。
Invoke-WebRequest -UseBasicParsing -Uri "https://raw.githubusercontent.com/pyenv-win/pyenv-win/master/pyenv-win/install-pyenv-win.ps1" -OutFile "./install-pyenv-win.ps1"; &"./install-pyenv-win.ps1"
pyenv / pyenv-win による Python の導入
MWings が対応する Python 3.12 以降(Lite版は3.11以降)の処理系を導入します。
導入できるバージョンの一覧を取得するには、下記を実行します。
pyenv install -l
ここでは例として、Python 3.12.4 を導入し、システム全体へ適用します。
pyenv install 3.12.4
pyenv global 3.12.4
導入済みバージョンの一覧を取得するには、下記を実行してください。
pyenv versions
pipx の導入
poetry のようなコマンドラインツールを隔離された環境で管理するために、pipx を導入します。
Linux
Debian系
sudo apt update
sudo apt install pipx
pipx ensurepath
pipから(Raspberry Piはこちらを推奨)
python3 -m pip install --user pipx
python3 -m pipx ensurepath
Fedora系
sudo dnf install pipx
pipx ensurepath
macOS
brew install pipx
pipx ensurepath
Windows
scoop install pipx
pipx ensurepath
必要に応じて Scoop を導入してください
Set-ExecutionPolicy -ExecutionPolicy RemoteSigned -Scope CurrentUser
Invoke-RestMethod -Uri https://get.scoop.sh | Invoke-Expression
poetry の導入
プロジェクトに対する処理系のバージョンやモジュールの依存関係を Node.js のような形で管理するために、poetry を導入します。
pipx install poetry
エラーを吐く場合
TypeError: __init__() got an unexpected keyword argument 'encoding'
といったエラーを吐く場合は、pipxをパッケージマネージャではなくpipから導入してみてください。
python3 -m pip install --user pipx
python3 -m pipx ensurepath
プロジェクトの作成
ここでは、プロジェクト名を mwtest
とします。
プロジェクトを作成するディレクトリへ移動し、下記を実行してください。
poetry new mwtest
mwtest
ディレクトリを生成します。
プロジェクトの設定
プロジェクトへ移動し、先ほど pyenv で導入した 処理系のバージョンを紐付けます。
poetry env use 3.12.4
MWings を導入します。
poetry add mwings
Linuxにて応答しない場合
次の環境変数の設定をお試しください。
export PYTHON_KEYRING_BACKEND=keyring.backends.null.Keyring
最も簡単なサンプルスクリプトの作成
まずは、先ほど紹介したスクリプト を動かしてみましょう。
import mwings as mw
twelite = mw.Twelite(mw.utils.ask_user_for_port())
@twelite.on(mw.common.PacketType.APP_TWELITE)
def on_app_twelite(packet):
print(packet.to_json())
twelite.start()
Lite版の場合
Raspberry Pi のserial0
を使う例を次に示します。
import mwingslite as mw
twelite = mw.Twelite("/dev/ttyS0")
@twelite.on(mw.common.PacketType.APP_TWELITE)
def on_app_twelite(packet):
print(packet.to_json())
twelite.start()
なお pyserial の制約により、
mw.utils.ask_user_for_port()
は/dev/ttyS0
を検知できません。
上記の内容で poetry が生成した __init__.py
と同じ階層に simple.py
を作成します。
📁 mwtest
└ 📁 mwtest
├ 📄 __init__.py
└ 📄 simple.py
最も簡単なサンプルスクリプトの実行
MONOSTICK を接続し、実行します。
poetry run python simple.py
複数のシリアルポートが存在する場合は、シリアルポートを選択してください。
JSON 文字列の出力を得ることができたら成功です。実際の出力例を下記に示します。
{
"time_parsed": "2024-02-20T03:16:50.150386+00:00",
"packet_type": "APP_TWELITE",
"sequence_number": 13699,
"source_serial_id": "810E0E23",
"source_logical_id": 120,
"lqi": 84,
"supply_voltage": 3249,
"destination_logical_id": 0,
"relay_count": 0,
"periodic": true,
"di_changed": [
true,
true,
false,
false
],
"di_state": [
false,
false,
false,
false
],
"ai_voltage": [
8,
272,
1032,
112
],
"mwings_implementation": "python",
"mwings_version": "1.0.0",
"hostname": "silverstone.local",
"system_type": "Darwin"
}
JSON 文字列の内容
mwings.parsers.app_twelite.ParsedPacket
キー | 値 |
---|---|
time_parsed | 受信時刻(デフォルトはUTC, ISO8601形式) |
packet_type | パケット種別 |
sequence_number | シーケンス番号(App_Tweliteの場合は時間) |
source_serial_id | 送信元シリアルID |
source_logical_id | 送信元論理デバイスID |
lqi | 電波通信品質(8bit) |
supply_voltage | 電源電圧(mV) |
destination_logical_id | 送信先論理デバイスID |
relay_count | 中継回数 |
periodic | 定期送信パケットか否か |
di_changed | 各デジタルインタフェースの入力変化の有無 |
di_state | 各デジタルインタフェースの入力状態 |
ai_voltage | 各アナログインタフェースの入力電圧 |
mwings_implementation | MWings の実装(将来のための情報) |
mwings_version | MWings のバージョン |
hostname | 受信したホストの名称 |
system_type | 受信したホストのシステムの種別 |
実用的なスクリプトの作成
simple.py
はあくまでも説明用のサンプルです。実用的なスクリプトではありません。
なぜなら、twelite.start()
がデータを受信するためのスレッドを作成するものの、これを終了する手段を用意していないからです。非常に読みづらいスクリプトでもあります。
次はより実用的なスクリプトを作成しましょう。それを実行したのち、内容を解説します。
今回は、下記の3点を条件とします。
これらを適用した例を下記に示します。
# -*- coding:utf-8 -*-
# Written for Python 3.12
# Formatted with Black
# MWings example: Receive data, print JSON, typed
from zoneinfo import ZoneInfo
import mwings as mw
# Main function
def main() -> None:
# Create a twelite object
twelite = mw.Twelite(mw.utils.ask_user_for_port())
# Use JST for received data
twelite.set_timezone(ZoneInfo("Asia/Tokyo"))
# Register an event handler
@twelite.on(mw.common.PacketType.APP_TWELITE)
def on_app_twelite(packet: mw.parsers.app_twelite.ParsedPacket) -> None:
print(packet.to_json(verbose=True, spread=False))
# Start receiving
try:
# Set as daemon thread
twelite.daemon = True
# Start the thread, Join to the main thread
twelite.start()
print("Started receiving")
while True:
twelite.join(0.5)
except KeyboardInterrupt:
# Stop the thread
print("Flushing...")
twelite.stop()
print("Completed")
if __name__ == "__main__":
# Call the main function
main()
Lite版の場合
import mwings as mw
をimport mwingslite as mw
としてください。上記を practical.py
として保存してください。
📁 mwtest
└ 📁 mwtest
├ 📄 __init__.py
├ 📄 simple.py
└ 📄 practical.py
実用的なスクリプトの実行
下記のコマンドを実行すると、simple.py
と同じようにして JSON 形式の出力を得ることができます。
poetry run python practical.py
ただし、今回はエラーを発生させずに Ctrl+C
で終了できるほか、time_parsed
は日本標準時になっているものと思います。
実用的なスクリプトの解説
コードの解説
practical.py
を解説します。
import
文
practical.py
では、2つのモジュールを import
しています。
from zoneinfo import ZoneInfo
import mwings as mw
zoneinfo.ZoneInfo
受信時刻のタイムゾーンを指定するために使用します。mwings
MWings ライブラリです。mw
に短縮して呼び出せるようにしています。Lite版の場合はmwingslite
です。
オブジェクトの作成
mw.Twelite
オブジェクトは、ホストへ接続された TWELITE の親機へアクセスするためのインタフェースとなります。
# Create a twelite object
twelite = mw.Twelite(mw.utils.ask_user_for_port())
mw.utils.ask_user_for_port()
関数は、ホストで利用できるシリアルポートの一覧を取得し、ユーザが選択したポートのファイルディスクリプタのパスや COM ポート名を返します。
明示的にシリアルポートを指定する方法
mw.Twelite
のコンストラクタへ、直接ファイルディスクリプタのパスや COM ポート名を指定します。
# Linux
twelite = mw.Twelite("/dev/ttyUSBx")
# macOS
twelite = mw.Twelite("/dev/cu.usbserial-MWxxxxxx")
# Windows
twelite = mw.Twelite("COMx")
シリアルポートを使用しない場合
ログファイルを解釈するような用途では、None
を指定します。
twelite = mw.Twelite(port=None)
タイムゾーンの設定
デフォルトでは、データの受信時刻を UTC として扱います。
practical.py
では、これを JST としています。
# Use JST for received data
twelite.set_timezone(ZoneInfo("Asia/Tokyo"))
ZoneInfo
には IANA のタイムゾーン識別子を渡してください。
受信ハンドラの登録
TWELITE の子機から送られるデータを処理するには、受信ハンドラを登録します。ここでは、超簡単!標準アプリ向けの受信ハンドラ内で受信したデータを JSON 形式に変換し、それを出力しています。
# Register an event handler
@twelite.on(mw.common.PacketType.APP_TWELITE)
def on_app_twelite(packet: mw.parsers.app_twelite.ParsedPacket) -> None:
print(packet.to_json(verbose=True, spread=False))
受信ハンドラは、任意の関数へ twelite.on()
デコレータ ? を適用することで登録できます。
mw.Twelite
オブジェクトの初期化後に同一のスコープへ定義する必要があります( practical.py
では main()
関数内)。受信ハンドラの定義箇所に制約を設けることで、グローバル空間の不要な汚染を避けるように誘導しています。受信ハンドラにおけるパケット種別の指定
受信ハンドラが受け取るデータの内容は、パケットの種別に応じて定義されたデータクラスに基づきます。
超簡単!標準アプリ
@twelite.on(mw.common.PacketType.APP_TWELITE)
def foobar(packet: mw.parsers.app_twelite.ParsedPacket):
# handle packets
リモコンアプリ
@twelite.on(mw.common.PacketType.APP_IO)
def foobar(packet: mw.parsers.app_io.ParsedPacket):
# handle packets
アリア(通常)
@twelite.on(mw.common.PacketType.APP_ARIA)
def foobar(packet: mw.parsers.app_aria.ParsedPacket):
# handle packets
キュー(通常)
@twelite.on(mw.common.PacketType.APP_CUE)
def foobar(packet: mw.parsers.app_cue.ParsedPacket):
# handle packets
キュー(動作パル・ムーブ/ダイス)
@twelite.on(mw.common.PacketType.APP_CUE_PAL_EVENT)
def foobar(packet: mw.parsers.app_cue_pal_event.ParsedPacket):
# handle packets
動作パル/キュー(動作パル・連続)
@twelite.on(mw.common.PacketType.APP_PAL_MOT)
def foobar(packet: mw.parsers.app_pal_mot.ParsedPacket):
# handle packets
環境パル
@twelite.on(mw.common.PacketType.APP_PAL_AMB)
def foobar(packet: mw.parsers.app_pal_amb.ParsedPacket):
# handle packets
開閉パル/アリア・キュー(開閉パル)
@twelite.on(mw.common.PacketType.APP_PAL_OPENCLOSE)
def foobar(packet: mw.parsers.app_pal_openclose.ParsedPacket):
# handle packets
シリアル通信(書式A、簡易)
@twelite.on(mw.common.PacketType.APP_UART_ASCII)
def foobar(packet: mw.parsers.app_uart_ascii.ParsedPacket):
# handle packets
シリアル通信(書式A、拡張)
@twelite.on(mw.common.PacketType.APP_UART_ASCII_EXTENDED)
def foobar(packet: mw.parsers.app_uart_ascii_extended.ParsedPacket):
# handle packets
アクト
@twelite.on(mw.common.PacketType.ACT)
def foobar(packet: mw.parsers.act.ParsedPacket):
# handle packets
受信ハンドラが受け取るデータクラス ParsedPacket
は、JSON 形式の文字列へ変換する to_json()
、辞書へ変換する to_dict()
、pandas データフレームへ変換する to_df()
といったメソッドを備えています。
ここでは、to_json()
メソッドを使って JSON 形式の文字列へと変換しています。
print(packet.to_json(verbose=True, spread=False))
オプション引数について
verbose
オプションを False
とした場合は system_type
などのシステム情報を出力しません。また、spread
オプションを True
とした場合は di_state
などの List-like な要素(mw.common.CrossSectional[T]
型)を個別に展開して出力します。ただし、加速度サンプルといった時系列データ(mw.common.TimeSeries[T]
型)は展開しません。
なお to_df()
に spread
オプションはありません。時系列でない List-like なデータは強制的に個別の列へ展開されるほか、時系列データは個別の行へ展開されます。
受信の開始と終了
mw.Twelite
は、threading.Thread
を継承しています
。practical.py
では、twelite.start()
が別のスレッドで受信処理を開始し、twelite.stop()
がそれを停止しています。
# Start receiving
try:
# Set as daemon thread
twelite.daemon = True
# Start the thread, Join to the main thread
twelite.start()
print("Started receiving")
while True:
twelite.join(0.5)
except KeyboardInterrupt:
# Stop the thread
print("Flushing...")
twelite.stop()
print("Completed")
twelite.daemon
を True
へ設定すると、受信処理を担うサブスレッドは デーモン化 されます。デーモンでない生存中のスレッドがすべて終了すると、 Python プログラム全体も終了します。ここではメインスレッド内で twelite.join()
を繰り返し呼ぶことで、メインスレッドを待機させています。
threading.Thread
から継承したスレッド関連の機能については、Python 公式ドキュメント を参照してください。メインスレッドは Ctrl+C
の入力を検知すると except
節で twelite.stop()
を呼び出し、受信処理を停止させます。twelite.stop()
はサブスレッドが受信ハンドラの呼び出しを終えるまで待機します。
join()
に関する注意
次のように while
ループを使用しない場合、Windows において Ctrl+C
を受け付けないことがあります。
try:
...
twelite.join()
except KeyboardInterrupt:
...
補足説明
PEP8 への対応
practical.py
や MWings のソースコードは、PEP8 に対応したコードフォーマッタ Black を使ってフォーマットされています。
正確には、完全に PEP8 へ準拠しているわけではありません
少なくとも2項目を違反しています。
- PEP8 では行の長さを最大79文字としています が、Black のデフォルトである最大88文字としています。コードを一行に収めるために変数名を縮めるなどして、品質が低下することを防ぐためです。参考:PyCon2015の講演
- PEP8 ではエンコーディング宣言を避けるべきとしています が、あえてエンコーディング宣言を行っています。日本語環境においては UTF-8 ではないファイルの混入する可能性が英語圏よりも高いほか、MWings はエンコーディング宣言を利用する Emacs を使って開発されているからです。なお、“should not have” のためか Black はこれを検知しません。
- PEP8には、この他にシーケンスの長さが0でないことを確認する際の
len()
を非推奨としているなど、その内容には議論の余地があります(PyCon JP 2024にてご教授いただいた James Powell 氏に感謝いたします)。
Black は一行の最大長を除き、いかなる設定も受け付けない頑固なフォーマッタですが、コーディング規約を策定・共有する手間を省くことができます。コーディングルールはしばしば物議を醸す議題ですが、そうした取るに足らない作業へ費やすリソースを有益な作業へ割り当てることこそが Python らしさの本質でしょう。
プロジェクトへ Black を追加するには、下記を実行します。
poetry add --group dev black
dev
グループを指定することで、開発時の依存関係であることを示しています。Node.js の devDependencies
と似ています。対象のファイルやディレクトリを指定して実行できます。
poetry run black mwtest
フォーマットせずに確認だけ行うこともできます。
poetry run black --check mwtest
型ヒントへの対応
practical.py
や MWings のソースコードは、型ヒント へ対応しています。
型ヒントとは
型ヒントは Python 3.5 で実装された機能です。動的型付けの Python における型アノテーションは実行時に意味を成しませんが、静的型チェッカによる検査はコードの品質と信頼性の向上に寄与します。
型ヒントに対応していないライブラリは、静的型チェッカのエラーを引き起こします。従来の パルスクリプト は未対応でした。
MWings ライブラリは、Python 公式の静的型チェッカである mypy を使用しています。
プロジェクトへ mypy を追加するには、下記を実行します。
poetry add --group dev mypy
こちらも Black と同様に、対象のファイルやディレクトリを指定して実行できます。
poetry run mypy mwtest
関連情報
実用的なスクリプトの応用
practical.py
をさらに発展させたスクリプトを公開しています。
mwings_python/examples at main
log_export.py
親機の出力を保存したテキストファイルを読み込み、解釈した結果を pandas データフレームへ保存し、最終的に CSV または Excel ファイルを出力します。Lite版は非対応。
コマンドラインツールとして利用できます
poetry run python log_export.py -h
usage: log_export.py [-h] [-x] [-v] [-s] INPUT_FILE
Parse a log file for App_Wings
positional arguments:
INPUT_FILE text file contains logs from App_Wings
options:
-h, --help show this help message and exit
-x, --excel export an Excel file instead of CSV
-v, --verbose include system information
-s, --sort sort columns in the output
rx_export.py
接続された親機の出力を受け取り、解釈した結果を pandas データフレームへ保存し、最終的に CSV または Excel ファイルを出力します。Lite版は非対応。
- CSV ファイルを選択した場合、すべての結果を一つのファイルへ保存します。
- 代わりに Excel ファイルを選択した場合、パケットの種別ごとに別のシートへ保存します。
コマンドラインツールとして利用できます
poetry run python rx_export.py -h
usage: rx_export.py [-h] [-x] [-v] [-s]
Log packets from App_Wings to csv or excel
options:
-h, --help show this help message and exit
-x, --excel export an Excel file instead of CSV
-v, --verbose include system information
-s, --sort sort columns in the output
rx_export.py
は受信したデータのすべてを一旦 pandas データフレームへ保存するため、長期間に渡る記録には適していません。しかし、Excel ファイルの出力に対応しています。rx_export_csv_durable.py
接続された親機の出力を受け取り、解釈した結果を送信元のシリアルIDごとに CSV ファイルへ追記していきます。Lite版は非対応。
コマンドラインツールとして利用できます
poetry run python rx_export_csv_durable.py -h
usage: rx_export_csv_durable.py [-h] [-v] [-s]
Log packets from App_Wings to csv, line by line
options:
-h, --help show this help message and exit
-v, --verbose include system information
-s, --sort sort columns in the output
rx_export_csv_durable.py
はデータを受信するたびに CSV ファイルを開き、データを追記します。rx_export.py
と異なり Excel ファイルの出力を行えませんが、長期間に及ぶ記録に適しています。rx_print_df.py
接続された親機の出力を受け取り、単に解釈した結果を pandas データフレームへ変換し、文字列として出力します。Lite版は非対応。
rx_print_dict.py
接続された親機の出力を受け取り、単に解釈した結果を辞書へ変換して出力します。Lite版も対応。
rx_print_json.py
接続された親機の出力を受け取り、単に解釈した結果を JSON 文字列へ変換して出力します。Lite版も対応。
tx_binary_uart.py
接続されたシリアル通信アプリの親機を介して、TWELITE UART へバイナリデータ [0xBE, 0xEF]
を送ります。Lite版も対応。
tx_blink_dip_led.py
接続された親機・中継機アプリの親機を介して、TWELITE DIP の DO1 ポートに接続された LED を点滅させます。Lite版も対応。
tx_blink_pal_notice.py
接続された親機・中継機アプリの親機を介して、通知パルの LED を各色で点灯させます。Lite版も対応。
API リファレンス
2 - TWELITE Wings API / MWings for 32-bit Arduinos
2.1 - TWELITE Wings API / MWings for 32-bit Arduinos
2.1.1 - TWELITE SPOT に使用する
2.1.2 - Arduino UNO R4 に使用する
ハードウェアの準備
TWELITE の準備
TWELITE UART や TWELITE DIP といった製品へ親機・中継機アプリ(App_Wings)を書き込みます。
書き込みには TWELITE STAGE APP を使用します。
2023年12月現在、シリアル通信によるアプリケーションID等の設定に対応した最新版の App_Wings(v1.3.0+)は TWELITE STAGE SDK に同梱しておりません(次期リリースに追加収録の予定です)。
下記をダウンロードしてから、書き込んでください。
- TWELITE BLUE 用:App_Wings_BLUE_L1305_V1-3-2.bin
- TWELITE RED 用:App_Wings_RED_L1305_V1-3-2.bin
Arduino UNO R4 との接続
TWELITE のピンのうち、下記を使用します。
- VCC(3.3V へ接続)
- GND(GND へ接続)
- TXD(D0/RX へ接続)
- RXD(D1/TX へ接続)
- RST(D11 など任意のポートへ接続)
- PRG(D12 など任意のポートへ接続)
ライブラリ付属のサンプルスケッチに準じた接続例を以下に示します。
ソフトウェアの準備
ライブラリの導入
Arduino ライブラリマネージャからインストールできます。
TWELITE SPOT マニュアル:MWings ライブラリの導入 を参照してください。
サンプルスケッチの動作確認
ライブラリには、各 TWELITE と通信を行うための簡単なサンプルスケッチを同梱しています。
例えば、超簡単!標準アプリ(App_Twelite)のデータを受信する場合は、メニューバーから以下のサンプルスケッチを開きます。
ファイル > スケッチ例 > MWings > Arduino UNO R4 > Receive > monitor_uno_r4_app_twelite
monitor_uno_r4_app_twelite.ino
// Monitor example for TWELITE with Arduino UNO R4: Receive data from App_Twelite
#include <Arduino.h>
#include "MWings.h"
const int RST_PIN = D11;
const int PRG_PIN = D12;
const int LED_PIN = D13; // Use on-board LED as indicator
const uint8_t TWE_CHANNEL = 18;
const uint32_t TWE_APP_ID = 0x67720102;
void setup()
{
// Initialize serial ports
while (!Serial && millis() < 5000); // Wait for internal USB-UART
Serial.begin(115200);
Serial.println("Monitor example for TWELITE with Arduino UNO R4: App_Twelite");
Serial1.begin(115200);
// Initialize TWELITE
if (Twelite.begin(Serial1,
LED_PIN, RST_PIN, PRG_PIN,
TWE_CHANNEL, TWE_APP_ID)) {
Serial.println("Successfully initialized TWELITE");
} else {
Serial.println("Failed to initialize TWELITE");
}
// Attach an event handler to process packets from App_Twelite
Twelite.on([](const ParsedAppTwelitePacket& packet) {
Serial.println("");
Serial.print("Packet Timestamp: ");
Serial.print(packet.u16SequenceNumber / 64.0f, 1); Serial.println(" sec");
Serial.print("Source Logical ID: 0x");
Serial.println(packet.u8SourceLogicalId, HEX);
Serial.print("LQI: ");
Serial.println(packet.u8Lqi, DEC);
Serial.print("Supply Voltage: ");
Serial.print(packet.u16SupplyVoltage, DEC); Serial.println(" mV");
Serial.print("Digital Input: ");
Serial.print(packet.bDiState[0] ? " DI1:Lo" : " DI1:Hi");
Serial.print(packet.bDiState[1] ? " DI2:Lo" : " DI2:Hi");
Serial.print(packet.bDiState[2] ? " DI3:Lo" : " DI3:Hi");
Serial.println(packet.bDiState[3] ? " DI4:Lo" : " DI4:Hi");
Serial.print("Analog Input: ");
Serial.print(" AI1:"); Serial.print(packet.u16AiVoltage[0]); Serial.print(" mV");
Serial.print(" AI2:"); Serial.print(packet.u16AiVoltage[1]); Serial.print(" mV");
Serial.print(" AI3:"); Serial.print(packet.u16AiVoltage[2]); Serial.print(" mV");
Serial.print(" AI4:"); Serial.print(packet.u16AiVoltage[3]); Serial.println(" mV");
});
}
void loop()
{
// Update TWELITE
Twelite.update();
}
/*
* Copyright (C) 2023 Mono Wireless Inc. All Rights Reserved.
* Released under MW-OSSLA-1J,1E (MONO WIRELESS OPEN SOURCE SOFTWARE LICENSE AGREEMENT).
*/
スケッチの詳細については、TWELITE SPOT 向けのスケッチ解説をご覧ください。大半の内容は共通しています。
以下は Arduino UNO R4 に固有の部分です。
ポート設定
6-8行目では、UART 関連を除く Arduino のポートを設定しています。
const int RST_PIN = D11;
const int PRG_PIN = D12;
const int LED_PIN = D13; // Use on-board LED as indicator
ポート | 役割 | 備考 |
---|---|---|
D11 | TWELITEのRST制御 | 上記の接続例参照 |
D12 | TWELITEのPRG制御 | 上記の接続例参照 |
D13 | 通信インジケータLED制御 | 内蔵LEDを使用 |
USBシリアルポートの初期化待ち
16行目では、USB シリアルポート(Serial
)の初期化を待っています。
while (!Serial && millis() < 5000); // Wait for internal USB-UART
Arduino UNO R4 は、UNO R3 のように USB シリアル変換 IC を搭載しておらず、本体の ARM CPU が USB シリアル変換機能を担っています。 そのため、シリアルポートの初期化が終了する前は通信を行えません。
関連情報
- MWings ライブラリ API リファレンス
- TWELITE SPOT スタートガイドのスケッチ解説(スケッチ内容の大半は共通しています)
- TWELITE SPOT マニュアルの一部のスケッチ解説(スケッチ内容の大半は共通しています)
2.1.3 - パケットパーサの拡張
概要
パーサを追加するためには、次の4点のファイルを編集する必要があります。
- (新規)パーサのヘッダファイル
parser/FooBarPacketParser.h
- 解釈するパケットの中身と、正規パケットの判定条件を書く
- (新規)パーサのソースファイル
parser/FooBarPacketParser.cpp
- パケットの中身を解釈する部分を書く
- (追記)本体のヘッダファイル
MWings.h
- パーサを追加する
- (追記)本体のソースファイル
MWings.cpp
- パーサを追加する
ソースファイルは GitHub から入手してください。
通常、Arduino のホームディレクトリ内にある libraries/
へ配置します。
GitHubにて、実際のコミット差分を閲覧できます。
Added App_Tag (ADC) packet parser · monowireless/mwings_arduino@aa5ecdb
パーサのヘッダファイル
parser/
以下にある既存のファイルを複製したうえで名称変更してください。
ここでは、parser/AppTagAdcPacketParser.h
を作成します。
#ifndef APPTAGADCPACKETPARSER_H
#define APPTAGADCPACKETPARSER_H
#include "MWings_Common.h"
/**
* @struct ParsedAppTagAdcPacket
* @brief Packet content for App_Tag (ADC)
*/
struct ParsedAppTagAdcPacket final : public mwings::ParsedPacketBase {
uint32_t u32RouterSerialId;
uint8_t u8SensorType;
uint16_t u16AiVoltage[2];
};
/**
* @class apptagadc::PacketParser
* @brief Packet parser for App_Tag (ADC)
*/
namespace apptagadc {
class PacketParser final : public mwings::PacketParserBase {
public:
// Check if the packet is from App_Tag (ADC)
inline bool isValid(const BarePacket& barePacket) const override {
if ((barePacket.u8At(12) == 0x10)
and (barePacket.u32At(18) == 0)
and (barePacket.u16PayloadSize == 22)) {
return true;
}
return false;
}
// Parse from bare packet
bool parse(const BarePacket& barePacket, mwings::ParsedPacketBase* const parsedPacket) const override;
};
}
extern apptagadc::PacketParser AppTagAdcPacketParser;
#endif // APPTAGADCPACKETPARSER_H
解釈するパケットの中身の記述
はじめに、App_Tag(アナログセンサ)の出力書式を確認してください。
出力データの例
:80000000B700628201015A0010DF08FD09A300000000E9
# | データ | 内容 | 値 | |
---|---|---|---|---|
: | char | ヘッダ | : | |
80000000 | 0 | uint32 | 中継機のシリアルID | 中継なし |
B7 | 4 | uint8 | LQI | 183/255 |
0062 | 5 | uint16 | 続き番号 | 98 |
8201015A | 7 | uint32 | 送信元のシリアルID | 0x201015A |
00 | 11 | uint8 | 送信元の論理デバイスID | 0x00 |
10 | 12 | uint8 | センサー種別 | アナログセンサー |
DF | 13 | uint8 | 電源電圧(mV) | 3330 mV |
08FD | 14 | uint16 | ADC1の電圧 | 2301 mV |
09A3 | 16 | uint16 | ADC2の電圧 | 2467 mV |
00000000 | 18 | uint32 | 未使用 | |
E9 | 22 | uint8 | チェックサム | 0xE9 |
char | フッタ | \r | ||
char | フッタ | \n |
インクルードガードやnamespace
、コメント文を置換したら、mwings::ParsedPacketBase
を継承し、パケットの中身を記述します。
ここでは、対象の子機に固有のデータを記述します。宛先の論理デバイスIDといった一般的なデータは、既にmwings::ParsedPacketBase
へ登録されているからです。
mwings::ParsedPacketBase
にある項目
型 | 名称 | 内容 |
---|---|---|
uint32_t | u32SourceSerialId | 送信元のシリアルID |
uint8_t | u8SourceLogicalId | 送信元の論理デバイスID |
uint16_t | u16SequenceNumber | シーケンス番号 |
uint8_t | u8Lqi | LQI |
uint16_t | u16SupplyVoltage | 電源電圧 (mV) |
電源電圧など、空き項目があっても構いません。
以下の部分において、App_Tag(アナログセンサ)に固有のデータを宣言しています。
struct ParsedAppTagAdcPacket final : public mwings::ParsedPacketBase {
uint32_t u32RouterSerialId;
uint8_t u8SensorType;
uint16_t u16AiVoltage[2];
};
正規パケットの判定条件の記述
mwings::PacketParserBase
を継承したapptagadc::PacketParser
を作成し、純粋仮想関数isValid()
をオーバーライドすることで正規パケットの判定条件を記述してください。
この条件に沿ったパケットを受信したとき、解釈を行います。
以下の部分では、センサ種別が0x12
(アナログセンサ)であること、未使用領域が0
であること、ペイロードの長さが22バイトであることを確認しています。
inline bool isValid(const BarePacket& barePacket) const override {
if ((barePacket.u8At(12) == 0x10)
and (barePacket.u32At(18) == 0)
and (barePacket.u16PayloadSize == 22)) {
return true;
}
return false;
}
パーサのソースファイル
parser/
以下にある既存のファイルを複製したうえで名称変更してください。
ここでは、parser/AppTagAdcPacketParser.cpp
を作成します。
#include "AppTagAdcPacketParser.h"
apptagadc::PacketParser AppTagAdcPacketParser;
bool apptagadc::PacketParser::parse(const BarePacket& barePacket, mwings::ParsedPacketBase* const parsedPacket) const
{
// WARNING: Note that there is NO RTTI
ParsedAppTagAdcPacket* const parsedAppTagAdcPacket = static_cast<ParsedAppTagAdcPacket*>(parsedPacket);
parsedAppTagAdcPacket->u32SourceSerialId = barePacket.u32At(7);
parsedAppTagAdcPacket->u8SourceLogicalId = barePacket.u8At(11);
parsedAppTagAdcPacket->u16SequenceNumber = barePacket.u16At(5);
parsedAppTagAdcPacket->u8Lqi = barePacket.u8At(4);
const uint16_t ecc = barePacket.u8At(13);
if (ecc <= 170) {
parsedAppTagAdcPacket->u16SupplyVoltage = 5 * ecc + 1950;
} else {
parsedAppTagAdcPacket->u16SupplyVoltage = 10 * (ecc - 170) + 2800;
}
parsedAppTagAdcPacket->u32RouterSerialId = barePacket.u32At(0);
parsedAppTagAdcPacket->u8SensorType = barePacket.u8At(12);
for (int i = 0; i < 2; i++) {
parsedAppTagAdcPacket->u16AiVoltage[i] = barePacket.u16At(2*i+14);
}
return true;
}
解釈する部分の記述
ヘッダファイル名やnamespace
を置換したら、parse()
の中身を記述します。
以下の部分では、データ書式に従ってパケットの中身を格納しています。
無線タグアプリ(アナログセンサ)の出力書式
# | データ | 内容 | 備考 |
---|---|---|---|
char | ヘッダ | : のみ | |
0 | uint32 | 中継機のシリアルID | 中継なしは80000000 |
4 | uint8 | LQI | 0 -255 |
5 | uint16 | 続き番号 | |
7 | uint32 | 送信元のシリアルID | |
11 | uint8 | 送信元の論理デバイスID | |
12 | uint8 | センサー種別 | |
13 | uint8 | 電源電圧(mV) | 電源電圧の計算を参照 |
14 | uint16 | ADC1の電圧 | |
16 | uint16 | ADC2の電圧 | |
18 | uint32 | 未使用 | |
22 | uint8 | チェックサム |
BarePacket.u8At()
などのメソッドを使って、素のペイロードからデータを取り出します。
// WARNING: Note that there is NO RTTI
ParsedAppTagAdcPacket* const parsedAppTagAdcPacket = static_cast<ParsedAppTagAdcPacket*>(parsedPacket);
parsedAppTagAdcPacket->u32SourceSerialId = barePacket.u32At(7);
parsedAppTagAdcPacket->u8SourceLogicalId = barePacket.u8At(11);
parsedAppTagAdcPacket->u16SequenceNumber = barePacket.u16At(5);
parsedAppTagAdcPacket->u8Lqi = barePacket.u8At(4);
const uint16_t ecc = barePacket.u8At(13);
if (ecc <= 170) {
parsedAppTagAdcPacket->u16SupplyVoltage = 5 * ecc + 1950;
} else {
parsedAppTagAdcPacket->u16SupplyVoltage = 10 * (ecc - 170) + 2800;
}
parsedAppTagAdcPacket->u32RouterSerialId = barePacket.u32At(0);
parsedAppTagAdcPacket->u8SensorType = barePacket.u8At(12);
for (int i = 0; i < 2; i++) {
parsedAppTagAdcPacket->u16AiVoltage[i] = barePacket.u16At(2*i+14);
}
static_cast
について
mwings::ParsedPacketBase*
からapptagadc::ParsedPacket
へのダウンキャストをdynamic_cast
ではなくstatic_cast
によって行っています。これは、ハードウェアの制約により実行時型情報が使えないことに起因しています。本体のヘッダファイル
既存のパーサに加えて、新たなパーサを追加します。
インクルード文の追加
パケットパーサのヘッダファイルをインクルードします。
//// AppTagAdcPacketParser for App_Tag (ADC)
#include "parser/AppTagAdcPacketParser.h"
初期化子リストの拡張
イベントハンドラをnullptr
で初期化します。
//// AppTagAdcPacketParser for App_Tag (ADC)
_onAppTagAdcPacket(nullptr),
終了処理の追加
イベントハンドラをnullptr
に戻します。
//// AppTagAdcPacketParser for App_Tag (ADC)
_onAppTagAdcPacket = nullptr;
イベントハンドラの登録メソッドの追加
イベントハンドラを登録するon()
メソッドを追加します。
//// AppTagAdcPacketParser for App_Tag (ADC)
inline void on(void (*callback)(const ParsedAppTagAdcPacket& packet)) { _onAppTagAdcPacket = callback; }
イベントハンドラの追加
イベントハンドラを格納するポインタを追加します。
//// AppTagAdcPacketParser for App_Tag (ADC)
void (*_onAppTagAdcPacket)(const ParsedAppTagAdcPacket& packet);
本体のソースファイルの編集
既存のパース処理に加えて、新たなパース処理を追加します。
イベントハンドラの初期化
begin()
の呼びだし時にもイベントハンドラが初期化されるようにします。
//// AppTagAdcPacketParser for App_Tag (ADC)
_onAppTagAdcPacket = nullptr;
パース処理の追加
対象のパケットを受信したときに解釈を行うようにします。
//// Start: AppTagAdcPacketParser for App_Tag (ADC)
if (AppTagAdcPacketParser.isValid(barePacket) and _onAppTagAdcPacket) {
ParsedAppTagAdcPacket parsedAppTagAdcPacket;
if (AppTagAdcPacketParser.parse(barePacket, &parsedAppTagAdcPacket)) {
_onAppTagAdcPacket(parsedAppTagAdcPacket);
}
}
//// End: AppTagAdcPacketParser for App_Tag (ADC)
変更の適用
スケッチをビルドしたときに、ライブラリも再ビルドされます。