Pythonでシリアル通信!Arduinoに文字列を送って、Arduinoから文字列を受け取る!
シリアル通信でLチカしたプログラムをアップデート。Arduinoに特定の文字列を送ると、それに対応した返答を返すというもの。
RS232Cを備える測定器をリモートコントロールするための練習プログラム。
greenhornprofessional.hatenablog.com
結果
Pyhtonから送りたい文字列を標準入力で入力すると、Arduinoから意図したとおりの返答を受け取れている。
送受信は30~40msで完了している。この数字が妥当なのか?などの深堀はまた今度。
また、送信する文字列の末尾にセミコロンをつけていない場合は時間が増える。これはArduino側 Serial.readStringUnitl()
がセミコロンを待つようにしているためで、これのタイムアウト(恐らく1sec)が影響している。
<= abc; 計測開始 送信完了:0.008sec => Input is abc => Return is ABC 受信完了:0.037sec <= abc 計測開始 送信完了:0.010sec => Input is abc => Return is ABC 受信完了:1.020sec <= ddd; 計測開始 送信完了:0.009sec => Input is ddd => N/A 受信完了:0.033sec
プログラム
Arduino側
void setup(){ Serial.begin(115200); } void loop(){ if(Serial.available()>0){ String input = Serial.readStringUntil(';'); Serial.print("Input is "); Serial.println(input); if(strcmp(input.c_str(), "abc") == 0){ Serial.println("Return is ABC"); }else if(strcmp(input.c_str(), "def") == 0){ Serial.println("Return is DEF"); }else{ Serial.println("N/A"); } } }
Python側
#SimpleSerial_003.py import serial from time import sleep import time def decoder(byte): str_array = [] str_array = byte.decode().rstrip('\r\n').split('\r\n') for i in str_array: print("=>",i) def main(): ser = serial.Serial('COM3', 115200, timeout=0.1) sleep(2) command = input() print("<=", command) t0 = time.time() print("計測開始") ser.write(bytes(command, encoding='ascii')) ser.flush() t1 = time.time() print("送信完了:{:.3f}sec".format(t1 - t0)) while True: if ser.in_waiting > 0: data = ser.read_all() break decoder(data) t2 = time.time() print("受信完了:{:.3f}sec".format(t2 - t1)) ser.close() if __name__ == '__main__': main()
コメント:今回のプログラムではser.flush()
は時間に寄与せず。
参考サイト
Serial.readStringUntil()
を紹介している
【prog】Arduino・シリアルで文字列受信するには:ゆうがたの「特にコレと言って」 - ブロマガ
文字列を比較するためのstrcmp
の紹介。コレ結構重要。
【C言語入門】文字列を比較する方法(strcmp、strncmp) | 侍エンジニアブログ
strからchar*に変換する方法。コレもかなり重要。
【C++入門】string型⇔char*型に変換する方法まとめ | 侍エンジニアブログ
Pythonでシリアル通信してArduinoをLチカさせる
とある測定器をRS232Cでリモートコントロールしたくて、Pythonのシリアル通信の仕方を学ぶ。まずは簡単なところからということで、Pythonから送られてきた数字(トリガー)をArduino側で識別してそれぞれに対してLチカの挙動を変える、というプログラムを作ってみた。
結果
0 : 消灯、1 : 点灯、2 : 5回点滅、その他 : 10回点滅、という条件分岐がちゃんとできている。
以下のPythonプログラムは"2"の動作のみ。
プログラム
Arduino側
void setup(){ Serial.begin(115200); pinMode(LED_BUILTIN, OUTPUT); digitalWrite(LED_BUILTIN, LOW); } void loop(){ void flicker(int, int); int input = Serial.read(); if(input != -1){ Serial.println(input); switch(input){ case '0': digitalWrite(LED_BUILTIN, LOW); break; case '1': digitalWrite(LED_BUILTIN, HIGH); break; case '2': flicker(5, 500); break; default: flicker(10, 100); } while(Serial.available())Serial.read(); } } void flicker(int n, int intval){ for(int i=0; i<n; i++){ digitalWrite(LED_BUILTIN, HIGH); delay(intval); digitalWrite(LED_BUILTIN, LOW); delay(intval); } }
Python側
#SimpleSerial_001.py import serial from time import sleep ser = serial.Serial('COM3', 115200, timeout=0.1) sleep(2) ser.write(bytes('2', encoding='ascii')) ser.close()
Pythonでフリー公開されているDLLを使ってみる
ctypes
を使ってDLLの呼び出し方を学ぶ。今回はフリーで公開されているDLLを使ってみる。前回よりも難しかった。
greenhornprofessional.hatenablog.com
使用させてもらったDLLは以下。文字列として書かれた式と変数の値を与えると式通りに計算してくる、というもの。
文字列よりの計算の詳細情報 : Vector ソフトを探す!
結果
式 x+y+zと、変数 x=10, y=2, z=0.1 を与えると、12.1が返ってきた。ちゃんと動いている。
今回はたまたま出来たというのが正直なところ。C/C++を勉強しないとこれよりも複雑なDLLは使えない。
プロトタイプに標準データ型以外の引数が書かれていると絶望的になる…
プログラム
# 31_CallCalcDll_001.py # python 3.8.1 # coding: utf-8 from ctypes import * p_ans = POINTER(c_double) p_siki = POINTER(c_char) lib = cdll.LoadLibrary('CALC11') lib.Num_Form_Calc.argtypes = (p_siki, c_double, c_double, c_double, p_ans) lib.Num_Form_Calc.restype = c_int siki = b"x+y+z" x = 10 y = 2 z = 0.1 ans = c_double() i = c_int() i = lib.Num_Form_Calc(siki, x, y, z, ans) print(i) print(ans) print(ans.value)
0 c_double(12.1) 12.1
Pythonでダミーファイルを大量生産する
ソフトウェアの動作確認のために大量の画像ファイルが必要になった。中身はどうでもよく、とにかく数が必要。
1つの元画像をコピーして連番をつける、というプログラムをちゃちゃっと作った。
プログラム
# 32_CopyAndRename_001.py # python 3.8.1 # coding: utf-8 import os import shutil num = 10 #Specify how many copies do you want. _dir = 'C:\Test' #Work directory src = 'lenna.jpg' #Original file name src_path = os.path.join(_dir, src) #Make the path like C:\aaa\bbb\ccc.jpg if os.path.isfile(src_path): #Check if the original file exists. for i in range(1, num+1): copy = str(i).zfill(3) + '.jpg' #Define sequential name like 001.jpg, 002.jpg, ... copy_path = os.path.join(_dir, copy) #Make a path for copy like C:\aaa\bbb\001.jpg. shutil.copyfile(src_path, copy_path) #Execute copy. print("Complete!") else: print("No file found.")
Pythonでdllを使う Windows API - MessageBox
マシンビジョンカメラをコントロールするソフトが作りたい。でもPython向けのAPIが公開されていない。調べてみたらctypes
でdllを使うことができるらしい。
とりあえず、WindowsのMessage Boxの呼び出し方を紹介してくれているサイトがあったので、参考にさせてもらった。
結果
以下のメッセージボックスを呼び出すことができた。メッセージボックスを表示させるだけならTkinter
よりもお手軽。
プログラム
# 30_WinMessageBox_001.py # python 3.8.1 # coding: utf-8 import ctypes mbox = ctypes.windll.user32 i = mbox.MessageBoxW( 0, "[テスト] 予期せぬエラーが発生しました", "Error Message", 0x00000002 | 0x00000010) if i == 3: print("中止を選択しました") elif i == 4: print("再試行を選択しました") else: #i == 5 print("無視を選択しました")
Pythonでpipが対応しているWheelファイルを調べる
ネットワークにつながっていないパソコンにモジュールを追加したい場合、そのモジュールのWheelファイル(.whl)を使うとことでインストールすることができる。Wheelファイルを適当なディレクトリに置いて、pipで指定してあげればよい。ただし、自分のpython環境にマッチしたWheelをもってくる必要がある。
どうやってそれを調べるか?ググればたくさん出てくるが、それ通りにはできなかったので忘備録を残しておく。※Python3.8
pip 20.1
環境における対応cpの調べ方の記事を見つけられなかったので苦労した
cpとはCPython versionの頭2文字のこと。Python = CPythonと思っていてよい。
What does version name 'cp27' or 'cp35' mean in Python? - Stack Overflow
要件
Pythonのバージョンを確認する
例えば、v3.8.1だったら『cp38』に対応したWheelファイルが必要になる。v3.7.* であれば『cp37』となる。
pipが対応しているcpを確認する
cp38に対応したWheelファイルを見つけても、pipがcp38に対応していないとインストールできない。
以下の.pyを実行すると対応cpがわかる。
#python 3.8.1 #pip 20.1.1 from setuptools import pep425tags print(pep425tags.get_supported())
[('cp38', 'cp38m', 'win32'), ('cp38', 'none', 'win32'), ('py3', 'none', 'win32'), ('cp38', 'none', 'any'), ('cp3', 'none', 'any'), ('py38', 'none', 'any'), ('py3', 'none', 'any'), ('py37', 'none', 'any'), ('py36', 'none', 'any'), ('py35', 'none', 'any'), ('py34', 'none', 'any'), ('py33', 'none', 'any'), ('py32', 'none', 'any'), ('py31', 'none', 'any'), ('py30', 'none', 'any')]
Pythonでグラフ画像から数値を読み取ってエクセルに出力する
シーンとしては、例えば、製品Aと製品Bの性能を比較するときに特性図(グラフ)を参照することがある。カタログやWebにグラフは掲載されているが、画像になっているので重ね合わせて比較することができず、なんとなくAの方が優れてるかな?なんてあいまいな感じに終わってしまう。メーカーに問い合わせても数値を提供してくれることは稀。まぁそりゃそうだろう。
ということで、画像になっているグラフから数値を読み取って、それをエクセルに出力するプログラムを作った。
結果
参考データとしては十分に使えるレベルで数値化できたと思う。画像処理の膨張縮小、線の検出で位置ズレが起きているはずなのであくまで参考データ。
左が入力画像、右が出力結果。数値はエクセルに出力するがグラフ化は手作業で。
注意事項としては、
- 第一象限のグラフしか対応していない。(マイナスを含むデータは非対応)
- 対数グラフは対応していない。
- 入力するグラフ画像は補助線よりもデータの線が太い必要がある。画像処理で消せなくなる。
- 入力するグラフ画像の淵はペイントなどで消す必要あり。上記の左画像の元データは以下。
プログラム
相変わらず、センスのなさに歯がゆい思いがする…まぁ動くのでよしとするが。
# 29_ExtractChart_001.py # python 3.8.1 # opencv-python 4.1.2.30 # coding: utf-8 # import cv2 import numpy as np import datetime import openpyxl #===================# # Define parameters # #===================# image = "f.png" k = 2 th = 100 y_max = 1 x_min = 350 x_max = 750 #=================# # Define function # #=================# # Function for extracting XY data from an imaged chart. def get_profile(): ## Add closing and threshold to erase auxiliary lines on the chart. img = cv2.imread(image, 0) kernel = np.ones((k, k), np.uint8) ret,img = cv2.threshold(img, th, 255, cv2.THRESH_BINARY) img = cv2.dilate(img, kernel,iterations = 1) img = cv2.erode(img, kernel,iterations = 1) # img = cv2.dilate(img, kernel,iterations = 10) # img = cv2.morphologyEx(img, cv2.MORPH_CLOSE, kernel) # img = cv2.morphologyEx(img, cv2.MORPH_OPEN, kernel) cv2.imshow("output", img) ## Get num of pixels of img. h:height w:width h, w = img.shape ## Array for XY data. [X, Y]. profile = [] ## print(type(profie)) -> <class 'list'> Not nparray! ## Get each XY-coordinate of each points on the line of the chart. for i in range(w): line = img[:,i] edge = [j for j in range(h-1) if line[j] != line[j+1]] ##Seach two inflection points (W to B and B to W) on a vertical line. if len(edge) < 1: val = 0 elif len(edge) == 1: if edge[0] > h/2: val = 0 else: val = h else: val = h - sum(edge)/2 ## Reverse Y-coordinate(top to bottom -> bottom to top). point = [i+1, val] ## XY data [X, Y] profile.append(point) ## Do unit conversion on X and Y coordinate. y = max(profile, key= lambda p:p[1])[1] ##Get maximum value of Y data. for q in range(w): profile[q][0] = profile[q][0] * (x_max - x_min) / w + x_min profile[q][1] = profile[q][1] * y_max / y return profile # Export 2Darray to xlsx format. def export_xlsx(arry): wb = openpyxl.Workbook() ws = wb.create_sheet(index= 0, title = "Line profile") ws = wb["Line profile"] ## I don't know how this works. wb.active = wb.sheetnames.index("Line profile") row = 1 for i in arry: celA = "A" + str(row) celB = "B" + str(row) ws[celA] = i[0] ws[celB] = i[1] row += 1 try: now = datetime.datetime.now() wb.save('LineProfile_{0:%Y%m%d%H%M%S}.xlsx'.format(now)) print("Save completed") except: print("Save failed!") #======# # Main # #======# if __name__ == "__main__": export_xlsx(get_profile())