UART機能によるPCとの連携
使用する製品
TWELITE DIP | TWELITE R2 |
TWELITE 親機/子機 | USB アダプター |
超簡単!標準アプリ | - |
2個 | 1個 |
なお、TWELITE DIP と TWELITE R2 のペアは MONOSTICK 単体と同等です。次の組み合わせでも構いません。
TWELITE DIP | MONOSTICK |
TWELITE 子機 | TWELITE 親機 |
超簡単!標準アプリ | 親機・中継機アプリ |
1個 | 1個 |
TWELITE STAGE アプリの導入
TWELITE STAGE アプリは、ファームウェアの設定や書き換えに加え、親機との送受信を評価するための機能を備えたツールです。
TWELITE STAGE アプリは、TWELITE STAGE SDK に含まれています。
TWELITE STAGE アプリによる受信
超簡単!標準アプリの子機が送信するデータは、DIx
/AIx
ポートの入力状態や電源電圧、送信元の論理デバイスIDといった情報を含んでいます。
シリアル文字列の表示
親機が子機から受信したデータは、親機がシリアル通信(UART)で出力する文字列を解釈することで取得できます。まずはこの文字列を表示してみましょう。
TWELITE STAGE アプリを立ち上げ、シリアルポート選択で親機を選択します。メインメニューの「1: ビューア」>「1: ターミナル」を開いてください。
子機のデータを受信すると、次のようなメッセージを表示します。
:78811501C98201015A000391000C2E00810301FFFFFFFFFB
親機が出力するこのような文字列を解釈することで、子機が送信したデータを得ることができます。
上記の文字列は次のデータを表しています
# | データ | 内容 | 値 | |
---|---|---|---|---|
: | char | ヘッダ | : | |
78 | 0 | uint8 | 送信元の論理デバイスID | 0x78 |
81 | 1 | uint8 | コマンド番号 | 0x81 |
15 | 2 | uint8 | パケット識別子 | 0x15 |
01 | 3 | uint8 | プロトコルバージョン | 0x01 |
C9 | 4 | uint8 | LQI | 201/255 |
8201015A | 5 | uint32 | 送信元のシリアルID | 0x201015A |
00 | 9 | uint8 | 送信先の論理デバイスID | 0x00 |
0391 | 10 | uint16 | タイムスタンプ | 約14.27 秒 |
00 | 12 | uint8 | 中継回数 | 0 |
0C2E | 13 | uint16 | 電源電圧 | 3118 mV |
00 | 15 | int8 | - | |
81 | 16 | uint8 | デジタル信号 | DI1 L DI2 H DI3 H DI4 H (定期送信) |
03 | 17 | uint8 | デジタル信号マスク | DI1 DI2 |
01 | 18 | uint8 | AI1 の変換値 | 16 mV |
FF | 19 | uint8 | AI2 の変換値 | 未使用 |
FF | 20 | uint8 | AI3 の変換値 | 未使用 |
FF | 21 | uint8 | AI4 の変換値 | 未使用 |
FF | 22 | uint8 | AIx の補正値 | AI1 0x03 |
FB | uint8 | チェックサム | 0xFB | |
char | フッタ | \r | ||
char | フッタ | \n |
なお、TWELITE の親機が出力する文字列は、基本的に次の形式へ従います。
ヘッダ | ペイロード | チェックサム | フッタ |
---|---|---|---|
: | 00 -FF の繰り返し | ペイロードのLRC8 | CRLF |
- すべて ASCII 文字※
- 先頭は
:
(0x3A
) - 末端は CRLF (
\r\n
/0x0D 0x0A
) - ビッグエンディアン
※ シリアル通信アプリのバイナリ書式を除く
標準アプリ ビューア
上記のような形式はコンピュータにやさしいものですが、人間が確認するにはこれを解釈する必要があります。TWELITE STAGE アプリには、超簡単!標準アプリの子機が送信したデータを表す文字列を解釈して表示する機能があります。後述の Python ライブラリはこの解釈を行います。
メインメニューの「1: ビューア」へ戻り、「2: 標準アプリ ビューア」を開いてください。
タイムスタンプと論理デバイスID、シリアルIDとDIx
/AIx
の値を確認できます。
TWELITE STAGE アプリによる送信
これまでとは反対に、親機から子機へ無線パケットを送信し、子機の出力状態を変更することもできます。
コマンダー
メインメニューの「1: ビューア」へ戻り、「5: コマンダー」を開いてください。
DIx
およびAIx
の入力をシミュレートしたパケットを送信できます。
使い方
- 送信先と入力状態を選択
- 送信をクリック
コマンダーは、PCから親機へシリアル通信によって文字列を送信しています。すなわち、標準アプリ ビューアとは逆の仕組みです。
この文字列も:
で始まり CRLF で終わります。詳しい書式は超簡単!標準アプリ マニュアルをご覧ください。
Python スクリプト
TWELITE STAGE アプリの「標準アプリ ビューア」および「コマンダー」は、どちらもシリアル通信によって文字列をやりとりしているに過ぎません。シリアル通信を利用できる環境であれば、自作のアプリケーションと親機を連携させることができます。
DI1
の入力状態を表示したのち、DO1
の出力を制御してみましょう。Python スクリプトを応用すれば、受信したデータを加工して保存したり、自作のソフトウェアから出力ポートを操作したりといった仕組みを実現できます。
MWings ライブラリの導入
MWings ライブラリは、TWELITE と Python スクリプトの連携を簡単に実現するためのライブラリです。具体的には、子機から受信したデータを表す文字列の解釈と、子機へ送信するデータの構築を行います。
PyPI からインストールできます。
Python スクリプトによる受信
TWELITE DIP のDI1
へ接続したボタンが押された際にメッセージを表示する Python スクリプトを実装してみます。
サンプルスクリプトの実行
下記の内容のスクリプト dip_show_di1.py
を作成してください。
# -*- coding:utf-8 -*-
# TWELTIE DIP start guide: receiving in python
import mwings as mw
def main():
twelite = mw.Twelite(mw.utils.ask_user_for_port())
@twelite.on(mw.common.PacketType.APP_TWELITE)
def on_app_twelite(packet):
if packet.di_state[0]:
print("DI1 Pressed")
try:
twelite.daemon = True
twelite.start()
print("Started receiving")
while True:
twelite.join(0.5)
except KeyboardInterrupt:
print("Flushing...")
twelite.stop()
print("Completed")
if __name__ == "__main__":
main()
スクリプトを実行すると、子機のDI1
が Low になったときに DI1 Pressed
と出力します。
実行時に複数の TWELITE R シリーズや MONOSTICK シリーズが接続されているときは、親機へ接続されたシリアルポートを選択してください。
poetry run python dip_show_di1.py
Multiple ports detected.
[1] /dev/cu.usbserial-R2xxxxxx TWE-Lite-R (Genuine)
[2] /dev/cu.usbserial-R2xxxxxx TWE-Lite-R (Genuine)
Select [1-2]: 2
Selected: /dev/cu.usbserial-R2xxxxxx
Started receiving
DI1 Pressed
DI1 Pressed
^CFlushing...
Completed
上記は macOS における例です。WindowsではCOMポート名を表示します。
サンプルスクリプトの解説
MWings ライブラリのインポート
import mwings as mw
短縮名 mw
を使って呼び出せるようにしています。pandas as pd
や numpy as np
と同様です。
オブジェクトの作成
twelite = mw.Twelite(mw.utils.ask_user_for_port())
親機をとやりとりするためのインタフェースとなる Twelite
オブジェクトを作成しています。
引数にはシリアルポートを指定しますが、ここでは mw.utils.ask_users_for_port()
ユーティリティを呼び出し、動的に決定しています。この関数はシリアルポートが存在しないときにエラーメッセージを出力し、1つだけ存在する場合はそのポートを、複数存在する場合はユーザが指定したポートを返します。
受信イベントハンドラの登録
@twelite.on(mw.common.PacketType.APP_TWELITE)
def on_app_twelite(packet):
if packet.di_state[0]:
print("DI1 Pressed")
超簡単!標準アプリの子機からのパケットを受信したときに呼び出されるイベントハンドラを登録しています。
イベントハンドラの登録は、Python のデコレータ(?)を使って行います。今回は超簡単!標準アプリ(App_Twelite)のデータを対象とするため、@twelite.on(mw.common.PacketType.APP_TWELITE)
を記述しています。この記述の直後に定義された関数がハンドラとなります。関数名は問いませんが、Twelite
オブジェクトの初期化後に定義する必要があります。
今回は子機のDI1
がLowになったことを検知するため、受信ハンドラが受け取る変数 packet
(mwings.parsers.app_twelite.ParsedPacket
)のうち、DIx
の状態を示す List-like なオブジェクト di_state
(デジタルインタフェースの状態)0番目の真偽値を判定しています。di_state
の各値は、True
のときに Low を示します。
その他のデータの取り扱い
packet
変数は、入力状態に加えて送信元の情報や受信側の情報も格納しています。例えばto_json()
を使って次のように改変すると、すべてのデータをJSON形式で出力できます。
@twelite.on(mw.common.PacketType.APP_TWELITE)
def on_app_twelite(packet):
print(packet.to_json(verbose=True, spread=False))
なお verbose
はシステム情報の省略を、spread
はList-likeなオブジェクトの展開を有効化します。
pandas を導入している場合は、to_df()
を使ってデータフレームを得ることもできます。
@twelite.on(mw.common.PacketType.APP_TWELITE)
def on_app_twelite(packet):
print(packet.to_df(verbose=False).to_string())
データの待機
twelite.daemon = True
twelite.start()
print("Started receiving")
while True:
twelite.join(0.5)
ここでは、別のスレッドで親機からのデータの待機を行っています。
Twelite
のオブジェクトは threading.Thread
のサブクラスであり、ここではその機能によってデーモンスレッドを立ち上げ、スクリプトを終了するまでメインスレッドをブロックしています。
なぜ while
ループおよび join(0.5)
としているのか
終了処理
print("Flushing...")
twelite.stop()
print("Completed")
Ctrl-C が押された際には、twelite.stop()
を呼び出すことでサブスレッドを終了しています。
Python スクリプトによる送信
次は、反対に TWELITE DIP のDO1
へ接続された LED をPCから点滅させる Python スクリプトを実装してみます。
サンプルスクリプトの実行
下記の内容のスクリプト dip_blink_led.py
を作成してください。
# -*- coding:utf-8 -*-
# TWELITE DIP start guide: blinking from python
from time import sleep
import mwings as mw
def main():
twelite = mw.Twelite(mw.utils.ask_user_for_port())
initial = {
"destination_logical_id": 0x78,
"di_to_change": [True, False, False, False],
"di_state": [False, False, False, False],
}
command = mw.serializers.app_twelite.Command(**initial)
while True:
command.di_state[0] = not command.di_state[0]
twelite.send(command)
print(f"Flip DO1: {command.di_state[0]}")
sleep(1)
if __name__ == "__main__":
try:
main()
except KeyboardInterrupt:
print("...Aborting")
スクリプトを実行すると、子機のDO1
が一秒おきに点滅します。
実行時に複数の TWELITE R シリーズや MONOSTICK シリーズが接続されているときは、親機へ接続されたシリアルポートを選択してください。
poetry run python dip_blink_led.py
Multiple ports detected.
[1] /dev/cu.usbserial-R2xxxxxx TWE-Lite-R (Genuine)
[2] /dev/cu.usbserial-R2xxxxxx TWE-Lite-R (Genuine)
Select [1-2]: 2
Selected: /dev/cu.usbserial-R2xxxxxx
Flip DO1: True
Flip DO1: False
Flip DO1: True
Flip DO1: False
Flip DO1: True
^C...Aborting
サンプルスクリプトの解説
MWings ライブラリのインポート
import mwings as mw
短縮名 mw
を使って呼び出せるようにしています。pandas as pd
や numpy as np
と同様です。
オブジェクトの作成
twelite = mw.Twelite(mw.utils.ask_user_for_port())
親機をとやりとりするためのインタフェースとなる Twelite
オブジェクトを作成しています。
引数にはシリアルポートを指定しますが、ここでは mw.utils.ask_users_for_port()
ユーティリティを呼び出し、動的に決定しています。この関数はシリアルポートが存在しないときにエラーメッセージを出力し、1つだけ存在する場合はそのポートを、複数存在する場合はユーザが指定したポートを返します。
親機へ送信するコマンドの作成
initial = {
"destination_logical_id": 0x78,
"di_to_change": [True, False, False, False],
"di_state": [False, False, False, False],
}
command = mw.serializers.app_twelite.Command(**initial)
超簡単!標準アプリの子機へ送信するパケットを生成するためのコマンドを表すデータ command
(mwings.serializers.app_twelite.Command
)を初期化しています。
辞書オブジェクト initial
は、コマンドの初期状態を表しています。ここでは、送信先の論理デバイスIDを 0x78
(全子機)としたうえで、DI1
を変更対象、High 状態としています。
データの送信
while True:
command.di_state[0] = not command.di_state[0]
twelite.send(command)
print(f"Flip DO1: {command.di_state[0]}")
sleep(1)
ここでは、コマンドデータの文字列への変換および親機への送信を1秒おきに行っています。
コマンドデータのうち、DOx
の状態を示す List-like なオブジェクト di_state
(デジタルインタフェースの状態)0番目の真偽値を反転させることで、点滅を実現しています。