TWELITE日記

2021年2月26日 (金)

TKinter afterメソッド:再帰的コールによる自動更新

GUIの表示を定期的に更新したくなるのはリアルタイムデータ処理では避けられない(と思う)。

GUI表示自体はTKinterで容易にできるようだけれど、それを定期的に更新する方法を調べて実装してみた。背景にあるのは、Pico+TWELITEによって定期的に取得する超音波距離センサー・データをGUI表示するためだ。

ここで使うのはTKinterが提供するafterメソッドだ。afterメソッドは、このメソッドコール後に実行するFunctionと、その実行までの待機時間をパラメータとして指定する。このFunctionとして、このafterメソッドを実行するFunction自体を指定すると再帰的なコールが実行される。つまり、指定した待機時間後のそのFunctionが繰り返し実行されるようになるわけだ。このFunctionにてGUIを描画すれば、定期ていなGUIアップデータ実現する。

class GUI(tk.Frame):

   def update(self):

      # GUIを描画するコード

      self.after(100,self.update) # 100ms後に自分自身を実行する。結果的にループ化する


if __name__ == "__main__":
   gui = tk.Tk()
   app = GUI(master = gui)
   app.update()  # updateをコールして再帰的コールをキックする
   app.mainloop() # 以後のイベント処理はmainloop内で実行される

ネット上に公開されている先人の知恵に学びながら作成した参考コードは以下からダウンロードできる。

ダウンロード - tktest04.py

また具体的な工作内容はこちらから参照できる。

aftetrメソッドを上手く使えばいろいろな事ができそうだ。

 

2021年2月17日 (水)

RaspiとTWELITEの会話の手助け

昨日悩んでいた事の一つがUARTのBaud Rate設定。

インタラクティブモードのSet UART Baudが38400になっているのでてっきりそうだと思っていたことが混乱の元だった。実際Raspberry Pi側のBaud Rateは115200に設定していて、これで通信が成立していたので???だった。今日、改めてモノワイヤレスのホームページを見ていると以下の記述があった。

Baudrate
BPS(Pin-20)でBaud Rateが設定できて、オープン状態(O)だと115200とある。G、つまりGNDに接続すると38400とある。今は無接続なので115200なわけだ。ちなみにインタラクティブモードで設定するのはGの時のBaud Rateのようだ。これで謎が一つ解けた。

もう一つ悩んだのがApp_Uartでシリアル通信モードになっているTWELITE DIPからのパケットはMONOSTICKは受信できるけれど、その逆方向でMONOSTICKから送ったパケットがTWELITEのUARTのTXに出力されないこと。理解が正しいのか分からないけれども、これは出来ないとの結論に落ち着いている。

そもそもシリアル通信モードで送信されるパケットには宛先もコマンドも含まれていない(ブロードキャスト)。例えばシリアル通信モードのTWELITEから送信されるパケットはこんな感じになっている。

:03002020202020203734333768

最初の03が送信元のDevice ID、その次が送信シーケンス番号で任意の値。ここではディフォルトの00が設定されている。それ以降はUARTに書き込んだ文字列で最後がチェックサムになっている。MONO STICKには出荷時のApp_Wingsが親機として機能している。これをシリアル通信モードのApp_Uartに置き換えれば通信するのかもしれない。でも、それをしたいわけではなくて、一般的な親機に対して複数のシリアル通信モードのTWELITEからシリアル通信モードパケットを送りたいだけなので、MONOSTICKのAppを書き換えることまではしなくていいのかなっておもった次第。

真偽はわかってないけれど、次のステップに進むにあたってはここまで確認できればいいのかなって思っている。

2021年2月16日 (火)

なかなか会話できないRaspiとTwelite

Raspberry Pi 4とTWELITEを会話させようとして一日終わってしまった。

Raspberry Pi 4 と TWELITEの接続は至って単純で4本繋ぐだけ。

Raspberry Pi 4 TWELITE
3.3V (pin-1) VCC (pin 28)
GND (pin-6) GND (pin-1)
TXD (pin-8) RX (pin-3)
RXD (pin-10) TX (pin-10)

しかし、これを通信させるのには一苦労した。おまけに理由は分からないけれどTWELITEを一つ殉職させてしまった。

分からなかったのはRaspberry PiのGPIOのUARTに信号を出力する方法。あと、TWELITEのアプリのApp_Uartが送出する信号フォーマット。

まず、Raspberry PiのSerialポート機能を有効化する方法は幾つかの書き込みがあった。それは以下コマンドを実行すること。

$ sudo raspi-config

ここで表示される画面で 5. Interfacing Optionsを選ぶ。
02161
次に P6 Serial を選ぶ。 
02162
ここでは<いいえ>を選ぶ。<はい>を選ぶとLinuxのコンソールアウトプットが怒涛の如くこのシリアルポートに流れてくる。
02163
結果的に以下の組み合わせ(login shellは禁止でSerialをイネーブルにする)に設定する。
 02164

ここからが分からなかった。どのポートにどのボーレートでデータを書き込むのか。
採用したのは以下のファイルの記述。これは上のraspi-configでlogin shellの利用で<はい>を選んだ場合にcmdline.txtに書かれる内容。ここではconsole=serial0, 115200 と書かれている。login shellで<はい>を選ぶと、GPIOのUARTに怒涛の如くコンソール出力が書かれて、それをTWELITEは送り続ける。この様子はMONOSTICKをTeraTermで観察しているとわかる。つまり、serial0を115200でオープンすればよいわけだ。
02165

で、以下のコードをRaspberry Piで動かしてみた

import serial

s = serial.Serial("/dev/serial0", 115200)
bs = '112233445566778899\n\r'
s.write(bs.encode())

結果はビンゴで、MONOSTICK側のTeraTermに以下が出力された。

:0300313132323333343435353636373738383939000043

Byte0はTWELITE DIPのDevice ID
Byte1は不明(親機のMONOSTICKのDevice ID=0だから宛先か?)
一番後ろはチェックサム
その前の2バイトの0000は不明

いずれにせよRaspberry PiのUART出力をTWELITEで送信することが出来た。

なお親機側は以下のコードで文字列化された16進数データをアスキー文字列に変換している。

import serial

s = serial.Serial("COM6", 115200, timeout=1)

try:
  while True:
    line = s.readline()
    line = line.decode('utf-8')
    if len(line):
      print("%s" % line)
      rdata = [line[i:i+2] for i in range(5,len(line)-6,2)]
      for x in rdata:
        n = int(x, 16)
        print(chr(int(n)),end='')
      print('\n')

except KeyboardInterrupt:
  pass

s.close()

明日ははMONOSTICK側からTWELITE経由でRaspberry Piにコマンドを送ろう。

2021年1月17日 (日)

TWELITE 中継機

TWELITEの中継機能を使ってみた。

親機となるMONO STICKに対して3個のTWELITE DIPを設置し、そのうちの一つを中継機モードに設定した。つまり、MONO STICKは子機2個と中継機1個の合計3個の信号を受信することになる。それらの様子は以下の写真の通り。後ろのPCのUSBポートにMONO STICKが接続されている。

Img04221_20210118095801

中継機として設定した2号機のDevice IDは2。中継機は入ってきた信号をそのまま送り出すだけなので、もはやDevice ID=2としての送信は行わない。よって、親機であるMONO STICKはDevice ID=2の信号を受信することはない。一方、Device ID=1の信号は、1号機と、その信号を中継する2号機から発信される。この識別は中継数(図中のRelay)でわかる。これが0ならば1号機からの信号、1ならば中継機である2号機からの信号と識別されるわけだ。
Relay1

これは3号機についても同じことが言えて、こちらもRelayが0だったり1だったりする。
Relay2

受信状況をモニターする限り、あくまでも傾向としての感触にすぎないけれども、中継信号(Relay=1)の受信頻度が少ない感じがする。ここは実際にどうなっているかはちゃんと調べないとわからない。

2021年1月16日 (土)

TWELITE マルチデバイス構成

TWELITE-DIPを2個追加した。

半完成キットだとピンの半田付けが楽しめてグッド。
Img04198

それぞれ2号機(Device ID=2)と3号機(Device ID=3)として、既に構成済みのDIP(以後、これを1号機と呼ぶ)に設定したApplication ID =20210108とともにDevice IDをTWELITE R2で書き込む。
Img04199

2号機(左)と3号機(右)には電源ON表示用の白色LEDに加えて、DO1に緑LEDを配線してある。1号機とともに電源を入れてMONO STICKのシリアルポートを読みだしてみるとDevice ID=1、2、3それぞれから信号が飛んできているのがわかった。
Img04203

Device IDを指定してDO1設定を行ってみる。上の状態は2号機のDO1をLow(緑LED点灯)にして3号機のDO1をHigh(LED消灯)にした状態。選択的に制御が出来ているようだ。

暫く(と言っても1分未満)受信しているとエラーが発生していた。受信プログラムをデータ長さ固定(49文字分)で書いていたらListのOut of Rangeが発生した。つまり、シリアルポートから取得したデータ長が49文字未満だったわけだ。正常受信した場合、文字列は:で始まる。以下が正常受信したときの出力表示結果。

:02811501A2810CD4DD001D19000C141A8000FFFFFFFFFF9C
length 49

一方Out of Rangeが発生した場合(以下)は見た感じ頭の数バイトが無くなっているようだ。

3D65000AE81880002CFFFFFFFE7E
length 28

まだ仮説にすぎないけれど、これはデバイスが増えたことで混信が発生して、その結果受信データが壊れるんじゃないだろうか。そもそも、個々のデバイスは全く独立して動作していて(時計同期はしていない)、個々のデバイスの都合で送信しているはずだ。つまり送りっぱなし。仮に混信が発生してもそれを知る術はないはず。となれば、受信側で受信データの有効性を確認して、有効でないと判断すれば、そのデータを捨てる必要がある。

複数デバイスからの受信内容をテーブルで表示するプログラムを書いてみた。受信したDevice IDごとにソートして、定期的な受信の度に更新する。
List

まぁ、データ通信の基本を書いているような気がするけれども、今更のように気が付いたりしているわけだ。もうちょっと先人の知恵を学んでから悩んだ方が良いみたい。

2021年1月11日 (月)

TWELITE GUI Toolの準備というか練習

繰り返しテストを行うとなるとGUI Toolが欲しくなってくる。そこでPythonでGUI Toolを作ってみた。PySimpleGUIが便利との先人の知恵にならって作ってみた。

環境はWindows 10でエディタはVisual Studio Codeにした。最初はCentOSで構築を試みたけれど、PySimpleGUIがうまく動かなかった。CentOS7にはPython 2.7がDefaultでインストールされているが、PySimpleGUIはPython3を前提にしていて、Python 2.7用のPySimpleGUIをインストールしたりしたが上手く動かず、、、Python 3もインストールしてみたけれども、これもいろいろと問題があり(yumが動かなくなったり)。なのでWindows 10にした。よかったこととしてはVisual Studio Codeをつかってみたこと。これは便利だ。

さて、まず受信データの表示を実装した。これは画面描写のタイミングで最新の情報を表示するようにした。そのためCOMポートからデータを読み込むときにreset_input_buffer()を実行している。MONO STICKは1秒単位でデータを受信するので、どんどんCOMポートのBufferにデータがたまってしまう。アプリがCOMポートからデータを読み出しとしてもバッファされた過去のデータを読み出してしまった。このために最新のデータを表示したい(COMポートから読み出したい)ので一旦バッファをクリアしてから、その直後の受信データを読むわけだ。
しかし子機が複数になった場合、この方法ではどの子機のデータを読み出すかわからない。バッファをクリアしてから暫くCOMポートを連続読みして、すべての子機のデータを拾う必要がある(とりあえずこれは今後の課題)。

Outputtest

データ送信側はData Out Port 1(DO1)からPort 4 (DO4)まで個別に制御するようにボタン配置した。アプリ起動時はすべてのポートはHIGH (0)に初期化している。以後、それぞれのポートの操作は記録していて、いずれかのポートの値を変更したとしても、他のポートはそれ以前の値を保つようにしている。

PWMは1000HzでのDuty比を0から100まで設定する。ポート設定値は100で0x0400になる。これもどのポートに値をせっていするかボタン選択している。他のポートの設定値はそのまま維持するようにしている。なお、アプリ起動時の初期値はすべてゼロにしている。

結論:

PySimpleGUIは便利だ。Visual Studio Codeも便利だ。これを使わない手は無い。

CentOSでなやんで半日以上費やしてしまった。CentOS8でも試してみるか、、、、でもそれが目的ではないし。しかし、久しぶりに書くコードは汚くて古臭い、、、、とほほ。

2021年1月10日 (日)

TWELITE Device IDの設定

通信ネットワークでは、たとえばドメイン名とデバイス識別子の組み合わせで通信相手を特定する。TWELITEも同様だ。それぞれがApplication IDとDevice IDに対応する。

Application IDはTWELITE出荷時にすべて(多分)のデバイスに同じIDが割り当てられている。一方Device IDは未設定のようだ(少なくとも自分が購入したTWELITE-DIPには設定されていなかった(*が表示された)。
昨日のI/O試験でMONO STICKからDIPへの送信は1バイト目の宛先アドレスを0x78で送信していた。この0x78は全子機への送信、つまりブロードキャストだった。で、今日はDIPにDevice IDを設定して選択的な通信ができることを確認する。

自分の実験環境について改めて書くと、PythonはCentOS上で実行している。これは今後機能設計を進めていくプラットフォームをLinuxにしたいからで、Linux上の開発環境の習得も同時に行いたいから。一方、使っているPCはWindows 10なので、VMWare PlayerをインストールしてVM環境を作っている。Tera Termを使ってWriter R2をアクセスしているので、R2が接続されているCOMポートをWindowsとVMとの間で接続切り替えをする必要がある(COMポート(COMポートに限らないけれど)はどちらか一方に排他的に接続されるので)。この切り替えは至って簡単で、VM Playerのプルダウンメニューの取り外し可能デバイスで操作すればよい。

Comportattach

Tera TermでBaud Rateを115200にセットして+を3回タイプしてInteractive Modeに入り、iをクリックしてDevice IDをセットする。とりあえず1をセットした。Sをタイプして保存し、再び+を3回タイプしてInteractive Modeを抜け、DIPをR2から外す。

Deviceid

COMポートに書き込む文字列の1バイト目の0x78を0x01に変更する。

変更前
sr.write(':7880010001007F000000000000XX' + '\r\n')

変更後
sr.write(':0180010001007F000000000000XX' + '\r\n')

0x01で通信ができ、その他0x78以外では通信ができないことを確認した。

 

2021年1月 9日 (土)

TWELITE I/O確認

 通信できることが分かったので、具体的にI/Oを行ってみたくなるのは人情。

TWELITE-DIPからMONO Stickに送られてくるデータを翻訳してみた。PythonのソースコードはRaspberry PiによるIoTシステム制御(森北出版)を参照させていただいた。

:788115017B810CCAC2006E33000BFB1680002BFFFFFFFEFA
[0] : src = 0x78
[1] : command = 0x81
[2] : packet id = 0x15
[3] : version = 0x01
[4] : LQI = 0x7B
[5-8] : src address = 0x810CCAC2
[9] : dist = 0x00
[10-11]: time stamp = 0x6E33
[12] : relay flag = 0x00
[13-14]: volt = 0x0BFB
[15] : reserved = 0x16
[16] : DI1-4 = 0x80
[17] : DI1-4_chg = 0x00
DI1=0/0 DI2=0/0 DI3=0/0 DI4=0/0
[18-21]: e1-e4 = 0x2BFFFFFF
[22] : ef1-ef4 = 0xFE
AD1=0696 AD2=-001 AD3=-001 AD4=-001 [mV]

これを見ていればDIPが送ってくるデータの確認ができる。DIPでは”超簡単!標準アプリ”が走っている。これは1秒間隔でデータを送ってくるのでこのデータ表示も1秒間隔で更新される。

次にテスト用回路を用意した。回路図は以下の通り。  Photo_20210109203801

電源がONになっているかを目視確認するためにLED1を配置し、DIPのマニュアル通りにDO1とPWM1にLEDを配置した。またDI1にSW1を配置した。DI1はプルアップされているのでこの回路になっている。更にAI1に温度センサー出力を繋いでいる。

この回路でSW1をONすると以下に変わる。
ON DI1=1/1 DI2=0/0 DI3=0/0 DI4=0/0
SW1をOFFすると以下になる。
DI1=0/1 DI2=0/0 DI3=0/0 DI4=0/0
つまりChange bit(の0/1の分母)は1のままの残るわけだ。これで1秒のインターバルよりも短い時間で状態遷移が発生しても記録が残るわけだ。
温度センサー(AD1)も手を当てていると数値が上がっていく。期待通りの動き。

次に出力制御を見てみる。その前に実際のブレッドボードは以下の通り。出力制御ではLED2とLED3の制御を行う。
Img041761

以下がサンプルプログラム。至ってシンプル。

import serial
sr = serial.Serial("/dev/ttyUSB0", 115200)
# 78 80 01 00 01 00FF 0000 0000 0000
sr.write(':7880010101007F000000000000XX' + '\r\n')
sr.close()

太字が重要で、最初の太字の00のBit0からBit3DO1からDO4に対応する。1がHighで0がLow。回路図の通りで、DO1がLowでLED3が点灯する。次の01はマスクで、01になっているのでDO1はマスクされていない状態。

次の00FFがPWMの値。3FFでMAXでここでの設定は7FなのでLDE2はかなり暗い。

なおMONO-WIRLESSには以下の記載がある。

1: 1バイト : 宛先アドレス(論理デバイスID) (0x00: 親機, 0x01 ~ 0x64: 子機ID指定, 0x78: 全子機)
親機から子機、または子機から親機への伝送に限ります。
2: 1バイト : コマンド番号 (0x80 固定)
3: 1バイト : 書式バージョン (0x01 固定, 将来のための拡張)
4: 1バイト : IO状態
b7..b3b2b1b0とした場合 b0/b1/b2/b3 が DO1/DO2/DO3/DO4)の設定値となり、0がHi、1がLoとなります。設定を有効化するために、続く IO状 態マスクのビットがを1に設定します。
5: 1バイト : IO状態設定マスク
b7..b3b2b1b0とした場合 b0/b1/b2/b3 が DO1/DO2/DO3/DO4)の設定値となり、0で対応するDOを設定しない、1で設定します。
6: 2バイト : PWM1の設定値 
0(0%)~1024(100%)または0xFFFF(設定しない)を与えます。
7: 2バイト : PWM2の設定値
8: 2バイト : PWM3の設定値
9: 2バイト : PWM4の設定値
10:1バイト : チェックサム

チェックサムは省略可能で、その場合は”XX”を入力すればよい。

チェックサムを計算したければ、上記MONO-WIRELESSの以下の解説に従えばよい。

データ部の各バイトの和を8ビット幅で計算し2の補数をとります。つまりデータ部の各バイトの総和+チェックサムバイトを8ビット幅で計算すると0になります。
チェックサムバイトをアスキー文字列2文字で表現します。
例えば 00A01301FF123456 では 0x00 + 0xA0 + ... + 0x56 = 0x4F となり、この二の補数は0xB1 です。(つまり 0x4F + 0xB1 = 0)

まぁ、いずれにせよ基本的なI/Oが出来ていることが確認できたわけだ。
めでたし、めでたし。

TWELITE パワーオン

半完成モデルのTWELITE-DIPの半田付けもなんとか完了し、パワーオンの準備が整った。

PCのUSBのCOMポートを介してTWELITEの初期設定を行う。初期設定したいのはApplication ID。グループIDみたいなもので、飛び交う信号の中から、同じApplication IDの信号を拾っていくことになる。なので通信を行うTWELITE間ではApplication IDを揃える必要がある。Default値は(多分)すべてのTWELITEが同じ値を持っているので敢えて設定しなくても通信は始められる。けれどもTWELITEがちゃんと動いているかを確認する意味で設定画面での操作を実行してみる。

TWELITE-DIPはTWELITE R2を介して、USB Type-CコネクタにてPCのUSBポートに接続される。
Img04158

R2にDIPを載せてUSBケーブルに接続する。
Img04157

Tera TermのシリアルポートをR2に接続されているCOMポートにし、Baud Rateを115200に設定する。
Terataerm


コンソール画面で+を0.5秒くらいの間隔で3回タイプしてセッションを無事開くことができると以下のInteractive Modeメニューが表示される。
Teraterm1   


ここでaをタイプしてApplication IDを設定する。ここではこの作業を実施した日をApplication IDに設定している。S(大文字)をタイプすることで設定がFlashに永続保存される。以下はSをタイプした後の画面。
Teraterm2

再び+を3回タイプすることでInteractive Modeを抜けることができる。この状態でR2からUSBケーブルを抜き、DIPの作業を終える。
Teraterm3

次はPC側のUSBドングルであるMONOSTICKの設定。
Img04162

こちらもR2同様に+を3回タイプすることでInteractive Modeに入る。
Teratermp1

R2同様にaをタイプしてApplication IDを設定し、Sをタイプして保存する。
Teratermp2

保存が完了するとその直後からR2との間で通信が始まり、以下のラインを1秒間隔で表示する(R2のDefaultでの信号送信間隔は1秒)。
ブレッドボードではDI1にスイッチをつなげてあり、スイッチを押すとGNDにつながる。以下の表示では1でスイッチを押して、2で離し、再び3で押して。4で離している。とりあえずR2が機能していることが確認できた。
Teratermp4

以上で基本形ができたので、以降実際にI/Oをすべくプログラムを用意していくことになる。

2021年1月 2日 (土)

PythonでMySQLを操作する - CentOS7/Python2の話

PythonでMySQLを操作したくなった。CentOS7での話。
(目的はTWELITEから送られてくるデータの保存用)

で、以下でインストールしてみた。

# yum install mysql-connector-python

--- 省略 ---

インストール:
mysql-connector-python.noarch 0:1.1.6-1.el7

完了しました!
#

以下、簡単なプログラムを用意した。すでにデータベースとしてTHTを用意してある。
# test.py
import pymysql.cursors

def main():
   connection = pymysql.connect(
      host='localhost',
      user='hit',
      password='hit',
      db='THT',
      charset='utf8',
      cursorclass=pymysql.cursors.DictCursor)

   connection.close()

if __name__ == "__main__":
main()

で、PythonでDBが操作できるか確認。残念ながら失敗。pymysql.cursorsがimportできない(見つからない)と言ってる。
$ python test.py
Traceback (most recent call last):
File "test.py", line 1, in <module>
import pymysql
ImportError: No module named pymysql

先人の知恵を調べてみるとPyMySQLはpipでインストールするのが良いとの言っている。PythonはPython Package Indexというライブラリーが用意されている。pipはそのライブラリーからインストールするツールということだ。で、pipのインストールから実施。なお、先人の知恵にはEPEL(Extra Packages for Enterprise Linux)を忘れないように!というアドバイスがあり、それに倣った。
$ sudo yum install -y epel-release
$ sudo yum install -y python-pip --enablerepo=epel

$ pip install mysql-connector-python

これでも同じエラーがでる(みつからない)。

で、以下を実行。
$ sudo pip install PyMySQL

これで初めてOKとなった。
ただしOKとなったのはPython2.
$ python test.py

Python3では同じエラーとなった。
$ python3 test.py

ImportError: No module named pymysql

結論:

以下でインストールせよ。ただしPython2でのみ有効。
$ sudo pip install PyMySQL