脱線が止まらない。RaspiでPythonに熱中
CNCマシンのプリント基板切削まであと少しというところで、Raspberrypi Zero W(以降Raspi0W)に寄り道し、今度は、USBとBluetoothシリアルのブリッジプログラムを書こうとして、Python(パイソン)にはまってしまった。完全な横道である。
PythonはRasPiのPiはPythonからと言われるようにRaspiの公式言語らしいが、これまで何となく縁がなかった。今や自慢にもならないが、所長は学生時代のFORTRANから50年間、ありとあらゆる計算機言語(今数えたら全部で25以上、アセンブラーだけでも8機種)をかじってきた(ひととおり動かした)。しかしPythonだけはまだ書いたことがない。
人が騒ぐとかえって冷めるという「へそ曲がり」が主な理由だが、「蛇」嫌い(Pythonはニシキヘビのこと)ということもあるかもしれない。本屋でO'ReillyのPython本の表紙を見て何度かのけぞった記憶がある。
冗談はともかく、ArduinoのUSBシリアルから、BlootoothのシリアルをつなぐRaspiでの仕掛けは、最初、スクリプトのリダイレクトで出来ると考えていたのだが、どうもリソースの競合で送受信は同時にできないような気がしてきた。実験レベルだが2つ定義するとはねられる。
で、丁度良い機会なのでPythonを知っておこうと気楽にプログラミングを書き始めた。ところがこれが面白いというか、うまく動かなくてすっかり深入りしてしまったのである(CかPerlで書いておけば良かったと思っても後の祭り)。
RaspberryPi Zero Wでは何故か不調。stretchバージョンが原因か(2/21/2018)
前記事の通りRaspi3では苦労の末、Bluetoothシリアルそのものは快調に動いた。しかしRaspi0Wではご機嫌が悪い。コマンドレベルでは特に問題なく動くのだが、Bluetoothのシリアル起動スクリプトをデーモンに入れて自動化しようとしたあたりから、つながらなくなった。
最初は良いのだが、一旦接続を切ると2回目からは、Can't bind rfcomm socket address already in useというエラーを吐いて何をやっても進まない(再立ち上げ必要)。だいたい、socketなどコマンドでいじれるレベルではないのでバグ臭い。
このメッセージをキーに検索すると、山ほど質問サイトがヒットする。海外が殆どである。決め手になるような対策は見つかっていない。特効薬はないようだ。wheesyあたりで良かったのが、Jessieで駄目になったというレポートがあるが、こんどのやつは、JessieからStretchでおかしくなっている。
そのうち遂にPCのWin10側までおかしくなった。ペアリングのときにシリアルディバイスではなくてオーディオ機器だとプロファイルを勘違いしはじめる。何度か繰り返すとうまくいくときがあるというのが悩ましい。どうも、道具として使えるほど安定していない感じである。 bluetoothの情報が、古いのと新しいのが混在し、どれを信じれば良いのかわからない。定番となるものがない。特定のハードウエアに特化し、CUIで動かす汎用的なhciconfigなどのコマンドはサポートが途切れているようだ。Raspi3では機嫌よく動いていたものが、Raspi0ではどうも不調である。
Jessie最終バージョンでRaspi0WのBluetooth完動(2/24/2018)
Raspi3とRaspi0Wが使うOSは共通である。ハードもこのあたりは変わっていない(と思う)。違うのはOSのバージョンだけである。Raspi0WはJessieではなく最新のStretchを入れている。これがうまくいかない原因である可能性が高くなってきた。
そこで、バージョンを落としてみることにした。 また、あの長時間のダウンロードかと覚悟していたが、偶然、Raspiの日本のミラーサイトがあることを発見した。いや助かった。早速、ここを試す。20分程度で1.5Gを落とすことができた。ひとつ前のJessieの最終版2017/7/10版でやってみる(ミラーサイトの一覧はこちら)
やっぱり、最新バージョンがおかしかったようだ。このOSでRaspi0のBluetoothは全く問題なくサイトの情報どおりにしっかり動いた。ただ、これはコマンド投入によるもので自動化はまだやっていない。しかし、これもうまく行く予感はする。
とはいえ、デーモン化はRaspi3でもうまく行っていない。Bluetoothのペアリングと接続はもう問題なくなったが、実際のCOMポートがつながらない時がある。Raspi0のブリッジの部品化はなかなか難しい。そろそろCNCに戻らねば、色んなことを忘れてしまいそうだ(いや、冗談でなくて)。
いやあ気難しい。ディバイスファイルがなくなってもプロセスが残ってしまう(2/25/2018) といいながら、諦めきれず、Bluetoothシリアルのトラブルシューティングを続けていた。段々、Linuxのことを思い出し、システムっぽいコマンドを打てるようになってきて、事情が明らかになってきた。
Linuxは基本的にはマルチタスクなので、コマンドを終了したつもりでもプロセスが残っているときがある。プロセスを表示するのはps -auxというコマンドである。しかし、このコマンドはメッセージ量が多く、問題点をみつけることが難しい。
しかし、Linux(UNIX)に慣れてきたので、ps -auxの出力をgrepでフィルターをかける
ps -aux | grep -i rfcomm などという芸当が出来るようになった(-iは大文字小文字を区別しない)。しかも一旦入れたらhistory機能(上向き矢印キー)で何度でも繰り返せる。
やっぱりそうだった。ディバイスファイル(/dev/rfcomm0)が残っていなくても、プロセス(rfcommというコマンド)が残っているではないか。これがこれまでの不調の原因だろう。ほっておくとこいつはいくらでも増えていく。もしかすると前のトラブルもOSのせいではなかったかもしれない。
通信の応答を待ち続けるrfcommのプロセスの残骸をps auxとkillでこまめに消して、Raspi0でも安定してシリアルが動くようになった。Raspiの接続要求コマンド(rfcomm listen ….)は、PCの端末から最初に接続を切れば、プロセスも消えることが分かった。
手順を守れば(起動は、必ずRaspi側からリダイレクトコマンドを入れ、切断するときは必ずPC側から切る)、ほぼBluetoothシリアルは安定してつながるようになったが、何かの拍子で動かなければ、WiFiのSSHをPCで開いて手当が必要である。まだ自動化や部品化までは道が遠そうである。
それにしても端末関係のLinux(UNIX)の処理は難しい。昔、LinuxのシリアルHOWTOを翻訳していてモデムと格闘していたことを思い出す。うまく動くと会社のLinuxマシンを電話で呼び出して自宅でデバッグすることが出来た。
閑話休題。電子工作以外の話(2/26/2018)
システムのややこしい話が続いたので、たまには息抜きに電子工作以外の話題。この時期に避けて通れない確定申告の話である。意外に思われるが、所長の確定申告はだいぶ前から手書きである。会社の申告ならともかく、個人の申告などの作業量はたかが知れている。最初パソコンでやってみたがすぐやめた。
一年に一回しかやらないパソコン操作である。覚えているわけがない。いちいちサイトなどで調べる時間は完全な無駄で、手で書いている方がよほど効率が良い。さらに重要なことがある。空欄に数字をいれるだけで確定申告が完成してしまうとそれに慣れてしまい、税制のしくみ(恐ろしさ)を知らずに過ごしてしまいそうだからである。
税制そのものの構造は、そんなに難しいものではない。収入額から、その収入を得るのに必要な経費と、社会的な生活経費(扶養や保険控除)を引いた所得額に、その額に合わせた税率をかけて税額を出し、そこから給料などで既に収めた源泉徴収額を引いた残りが確定申告額である。
しかし、この税額を決める過程が実に悩ましい。税額を出すだけなら単純な計算、所得額の大小に応じた税率をかけて、その金額から一定額を引くことで求められるが、ではこの所得額ではどれくらいの税率になるかを調べようとすると途端に難しくなる。
税率と所得の関係は、税率 = 最初の税率 -( 一定額 / 所得額 ) という式で、これはグラフにすると1/Xなどで代表される双曲線で表される関数である。つまり税率は、所得額が195万以下は一定だが、195万以上は段階的な曲線を描いて最高税率の45%まで上がっていく。
株の配当などは税率が一律なので、総合課税でも分離課税でも変わらないが、雑収入(講演料や執筆料)などは最初にとられている源泉徴収分が払いすぎなので申告したら戻ってくるのか、無申告のままの方が良いのかは、そう簡単には答えが出ない。総所得によって税率が変わるからである(本当は不労所得である株の配当こそ、収入に応じた高い税率とするべきなのだが)。
給与所得者は大元で完璧に把握されており、必要経費も厳重な制限があるので税金を節約することは絶望的である(個人事業者申請をして抵抗することもできるが)。確定申告のささやかな楽しみの一つがどれだけ税金を安くできるか(脱税ではなく節税)なのだが、パソコン操作ではこの楽しみの仕組みを知ることなく終わってしまう。
パソコンで言われるままに金額を入れていけば、すぐ税額が出るのは確かに便利だ。しかし、なるだけしくみを見せないで、向こうの都合の良い税額を決めようという魂胆が透けて見えて所長はいつも不愉快になる。
それはともかく、確定申告の作業と合わせて、この時期忙しかったのが、冬季オリンピックの観戦だった。今年のオリンピックは何かと話題が多かった。メダルの数も最大のようでご同慶の至りである。女子スケートの団体金メダルなど、いかにも日本人らしい緻密な協同作業の成果である。
瞬間芸の多い冬季五輪競技でカーリングだけは実にのどかな雰囲気で、大人気になったのも、うなずける。それにしても、メダリストと4位以下の選手の扱いがこんなに違うのは、はたから見ているとお気の毒としか言いようがない。なぜこんなに違ってしまうのか、人間の心理を分析する必要がありそうだ。
それはそうと、CNCマシンがそろそろ埃をかぶっている。早く工作に戻ろう。
Pythonのプログラミングを始める(3/2/2018)
Raspi0wのbluetoothシリアルはまずまず安定してつながるようになった。一方向だけなら、リダイレクトだけで、USBにつないだESP8266の時報(DS3231を使ったやつ)がbluetoothを通してPCの端末(TeratermのCOM10)に表示されるようになった。
しかし、Raspi3でも経験した通り、もう一方のPCからESP8266へ返すコマンド類は出すことが出来ない。/dev/XXXを2回リダイレクトに使わなければならないからである。一方向をリダイレクトして、もう一方を指定すると「そのファイルは使用中」というメッセージではねられる。
cat /dev/AAA > /dev/BBB
cat /dev/BBB > /dev/AAA ====>ここでエラー
まあ、考えてみれば当たり前の話で、何かうまい回避策があるのかもしれないが、ウェブにはそれらしい情報は見当たらなかった。これはやっぱりプログラムを書かねばならないか。まあ、たいしたプログラムではないから、Cで書いてもそう手間はかからないだろう。
ここで前から気になっていたPythonが頭に浮かんだ。こいつはマルチスレッドも出来るようだし、マスターする絶好の機会に恵まれた感じがする。もともとRaspiを始めたのは、ArduinoのUSBをワイヤレスでつなぎCNCマシンを離れたところから動かそうという寄り道で、Pythonはさらに別の横道になるが、もう止められない。Pythonを学ぶ機は熟したようである。
情けない。PythonのLチカでお粗末ミス(3/8/2018)
PythonはRaspiでは標準装備だし、PCにもいつのまにかPythonが導入されていた(何かのパッケージをダウンロードした時、勝手に入ったものらしい)。勉強するのに情報は事欠かない。ネット上にはPython情報が溢れかえっている。
利用させてもらっているのに文句を言うのは失礼だが、残念ながら殆どが初心者か初級者向けの入門コースの情報で、すこし難しいことをやろうとすると、途端に少なくなり、海外まで足を延ばさないと必要な情報が得られない。
まあ、これはどの分野でも同じで覚悟はしていたが、pythonはコンピューター言語の入門コースという触れ込みらしく特に多い。Pythonが入門用の言語というのは、実は大いに異論があるのだが、ここでこれにこだわると話が完全に発散してしまうのでこれ以上はやめておく。
ひとわたりPythonを調べてシリアルブリッジくらいなら簡単に書けそうなことはわかった。でも教えて頂いたサイトの先人の方々を無視するのは失礼になる。敬意を表して定石通りLチカから始めることにした。
ところが、これが難航したのである。デバッグに半日かかった。原因は、全く単純なコーディングミスである。情けないというか、ヤキがまわったというか。年はとるものではない。ここに書くのもお恥ずかしいミスをまあ聞いて下さい。
LチカなのでI/Oピンを直に触る必要がある。RaspiのPythonでは、GPIOの制御はRPiというライブラリがあって(他にも色々ある)、これをimportすることになっている。
import RPi.GPIO as GPIO
とやってモジュールを組み込み、数行ばかりの簡単なLEDの点滅のプログラムを動かそうとした。すると、
Traceback (most recent call last):
File "led-flash.py", line 4, in <module>
GPIO.setmode(RPi.GPIO.BCM)
NameError: name 'RPi' is not defined
というエラーで止まった。何い、GPIOを操作するRPiライブラリーが入っていない?というのでいくつかのRPiライブラリーを入れる手順を実行し、すべてエラーもなくインストールされた。しかし、ことごとく同じ上記のエラーではねられる。最後はpythonパッケージの入れ替えまでやった。勿論それでも動かない。
このエラーステートメントを注意深く読めば、こんなに大騒ぎしなくても良いのだが、pythonの事情に明るくないので、うっかり見過ごしていた。最後の一行だけしか目に入っていない。
その前の行に問題の一行がある。GPIO.setmode(RPi.GPIO.BCM)でRPiが定義されていないと言っている。
そう、ここのRPiは不要というより間違いである。前に、import RPi.GPIO as GPIOとしているのだから、モジュールは、RPi.RPi.GPIOを探すことになる。
このRPiを削除してRaspi0Wは何事もなくLEDの点滅を始めたことは言うまでもない。情けなくて暫く立ち上がることもできなかった。このショックから立ち直るため、最初からpythonの勉強をすることを決意し、参考書まで買ってしまった。
「エキスパートPythonプログラミング」である。しかし、これはまた完全に失敗した。全く高度すぎて歯が立たない。絶版になっているらしく、古本でも新品より高い。いつものことながら、参考書の選定がすべて間違っている。負け惜しみをいうようだが、中級向けの参考書って本当に少ないと思う。
Pythonはマルチスレッドにして送受信とりあえず開通(3/10/2018)
pythonは対話形式のインタープリターがご先祖のようで、キーボードなどの入力ディバイスの操作は意外にも機能が揃っていない。画面の自由な表示や、ゲーム機のようなキーボードの押下のような動作は標準機能にはなく、すべて拡張機能である。
例えば、シリアルの送信側のキー操作は、キーが押されたタイミングを検知してプログラムを動かさないとキー入力を待っていたらプログラムは先に進まないことになる。ところが探し回ったが、Windowsにはあるキーが押されたことを知らせる機能がLinux/Macにないのだ。
これや、これのような拡張機能はあるが、一定のキーの押下は検知できてもすべての印字可能なキーの動きをセンスしてくれる一般的な関数が見当たらない。
こうなったら、無理にシングルスレッドにこだわることもないだろう。Pythonは一つのプログラムで複数のスレッドを動かせるらしい。 調べてみたら、それほど難しくなさそうだ。
実際に入れてみたらPythonのマルチスレッドは思ったより簡単で、すぐ動いた。さすがPythonである。スレッドを2つにしたおかげでループの中の処理も非常に簡単になった。シングルスレッドでは、中のループを途中で止めないような細心の注意が必要だが、別々なら何の配慮も必要ない。ただ、相手の送信を待ち、データが入ってくれば送るだけで済む。
テストしてみる。順調に双方のシリアルポートが開き、USBにつないだESP8266の時報プログラムがBluetooth経由でPCのターミナル(Teraterm)に出てきた。次はPC側からESP8266の時刻の較正入力である。
出た。しかし、行送りがうまく行かないせいか文字の出方がおかしい。送受信とは全く別ルーチンで動いているはずなのに、送り側の文字を入れないと案内のメッセージが出てこない。ひとつづつ遅れるので較正のプロセスはうまく動いていない。
しばらく、これで悩んだ。サイトを巡り歩いているうちに、原因らしいものをみつけた。pythonのコンソールによる出力はバッファリングされているというのだ。つまり、ブランクや改行を伴わない裸のデータを出力するときは、
sys.stdout.write()
というステートメントを使うのだが、これだけではまだのバッファリングされて本当の画面には出てこない。以下のコマンドが必要だという。
sys.stdout.flash()
要するにキューに溜まったデータを常に吐き出しておかないとおかしくなる。
この一行を加えて、Raspi0Wのシリアルブリッジは無事、完成した。暫く、USB側に色々な機械をつないで遊ぶ。至福の時間である。まだ、実際のCNCマシンとCandle(GBRL)でのテストが待っているが、とりあえず一段落である。いやいや今回も波瀾万丈だった。
以下は、当研究所初めてのPythonプログラムです(USBとBluetoothのシリアルをブリッジする)
Raspiのコマンドないしはスクリプトとしてお使いください。Bluetoothのシリアル化と、ポートの監視(listen)コマンドについては、こちらや、こちらのサイトが詳しくて親切なのでそちらをどうぞ。
#!/usr/bin/env python #coding: utf-8 # # LABO Gataro 3/11/2018 # Serial bridge between # USB and Bluetooth # import serial import time import thread import sys TIMEOUT = 0.0 def comOpen(tport): try:
comtmp = serial.Serial( port=tport,
baudrate = 115200, bytesize=8,
parity = 'N', stopbits = 1,
timeout = TIMEOUT, writeTimeout = TIMEOUT )
return comtmp except: print "Failed Open /dev->" + comtmp.portstr def sendtxt() :
ble.reset_input_buffer()
while True:
kb = ble.read(1)
com.write(kb)
com.flush()
if kb == '~' :
print "..TX Stopped.." sys.exit() #------------------------------------------------------
def main() :
com.reset_input_buffer() ble.reset_input_buffer() thread.start_new_thread( sendtxt, ()) print "Threading start(sendtxt)... " line = "" try: while True: if com.in_waiting > 0 : line = com.read(1) # sys.stdout.write(line) # test print on Raspi # sys.stdout.flush() ble.write(line) except: print "All thread stopped..." #======================================================= if __name__ == '__main__' :
com = comOpen('/dev/ttyUSB0') print "USB Comport Opened... " + com.portstr com.reset_input_buffer() ble = comOpen('/dev/rfcomm0') print "BlueTooth Com opened..." + ble.portstr main()
| 固定リンク
| コメント (6)
| トラックバック (0)
最近のコメント