今回はPySide2とPySerialを組み合わせて使うメモ
やることとしてはQMLで作成したGUI側でイベントを発生させてシリアルで何か送信してマイコンを制御するという感じ
つくったのはこんな感じのやつ
COMの選択をしてオープンをすると5~7のオブジェクトが表示される仕組み クローズすると消えるようになってる
コード全文は下に貼るのでちょい特殊な部分だけ解説していく流れで行きます。
尚基礎に関しては一番下に過去の記事貼っていますのでそちらを参考に環境構築などして下さい
Python側実装説明
class SerialComport(QtCore.QObject): def __init__(self, parent=None): super(SerialComport, self).__init__(parent) self.flag = 0 self.instance = [] @QtCore.Slot(result = 'QVariant') def comlist(self): self.ports = list_ports.comports() self.devices = [info.device for info in self.ports] self.instance.clear() #clear list for i in range(len(self.devices)): self.instance.append(serial.Serial(port=self.devices[i],baudrate=9600))#instance packing self.instance[i].close() return self.devices
self.ports = list_ports.comports()でCOMの取得をしている。
self.instance.appendってのでインスタンスを配列みたいに管理できる。
これのおかげで self.instance[1].closeみたいな感じでアクセス可能になるわけ.
めんどくさかったのでボーレートは9600固定(GUI側でボーレート指定すれば実現は可能)
return self.devicesでQt側にlistで返している('QVariant'なので色々返せる)
Pyserialのほうは殆どAPI使ってるだけなので特にないけど送信はこんな感じで書いてる
@QtCore.Slot(str,int,int,int) def slider_changed(self, arg1,r,g,b): for i in range(len(self.devices)): if (self.instance[i].port == arg1): self.instance[i].write(b.to_bytes(1, 'big')) self.instance[i].write(g.to_bytes(1, 'big')) self.instance[i].write(r.to_bytes(1, 'big'))
Qt(qml)側の説明
function comupdate(){ comboBox.model.clear() var device = SerialComport.comlist(); for(var key in device){ comboBox.model.append({text:device[key]}); } comboBox.currentIndex = 1; }
起動時の処理を毎度書くのがめんどかったのでfunctionにした。
あとはcomboBoxのmodelプロパティにアクセスしCOMリストを更新している感じ
function part_visible(){ radioDelegate.visible = true radioDelegate1.visible = true radioDelegate2.visible = true radioDelegate3.visible = true slider.visible = true slider1.visible = true slider2.visible = true rectangle.visible = true } function part_hide(){ radioDelegate.visible = false radioDelegate1.visible = false radioDelegate2.visible = false radioDelegate3.visible = false slider.visible = false slider1.visible = false slider2.visible = false rectangle.visible = false }
これはプロパティ表示/非表示の命令であり、各種コンポーネントのvisibleプロパティにtrue/falseしているだけ。
まとめ
Pyserial + PySide2で簡単なCOMアプリケーションを構成することができた。
結構手抜き実装なのでちゃんとやるならtryとか入れたほうがいいと思う。あとはisOpenのケアをもっとまじめにするとかね。
エンディアンとかあんまし気にしてないので間違ってるかも。なのでそこはPyserialのドキュメントちゃんと読んでください。
最後に動作例の動画でも貼っときます
こんな感じ pic.twitter.com/Bm2PucSjTF
— おながわ (@onagawadosu) October 3, 2019
マイコン側参考コード
ハードはKinetis FRDM-K22FでソフトはMbedを利用して書いてます。
/* mbed Microcontroller Library * Copyright (c) 2018 ARM Limited * SPDX-License-Identifier: Apache-2.0 */ #include "mbed.h" #include "stats_report.h" PwmOut led1(LED1);//r PwmOut led2(LED2);//g PwmOut led3(LED3);//b #define SLEEP_TIME 500 // (msec) #define PRINT_AFTER_N_LOOPS 20 Serial pc(USBTX, USBRX); // main() runs in its own thread in the OS int main() { SystemReport sys_state( SLEEP_TIME * PRINT_AFTER_N_LOOPS /* Loop delay time in ms */); int count = 0; while (true) { char b = pc.getc(); char g = pc.getc(); char r = pc.getc(); led1 = 1.0-1.0/255.0f*(float)r;//R led2 = 1.0-1.0/255.0f*(float)g;//G led3 = 1.0-1.0/255.0f*(float)b; //B } }
参考用コード全文(Python側)
import sys import os from PySide2 import QtCore, QtWidgets, QtQml from serial.tools import list_ports import serial class SerialComport(QtCore.QObject): def __init__(self, parent=None): super(SerialComport, self).__init__(parent) self.flag = 0 self.instance = [] @QtCore.Slot(result = 'QVariant') def comlist(self): self.ports = list_ports.comports() self.devices = [info.device for info in self.ports] self.instance.clear() #clear list for i in range(len(self.devices)): self.instance.append(serial.Serial(port=self.devices[i],baudrate=9600))#instance packing self.instance[i].close() return self.devices @QtCore.Slot(str) def comopen(self,arg1): for i in range(len(self.devices)): if(self.instance[i].port==arg1): self.instance[i].open() #COM open @QtCore.Slot(str) def comclose(self, arg1): for i in range(len(self.devices)): if (self.instance[i].port == arg1): self.instance[i].close() #COM close @QtCore.Slot(str,result='int') def comlistchanged(self,arg1): for i in range(len(self.devices)): if(self.instance[i].port==arg1): #checking which COM port is selected. if(self.instance[i].isOpen() == True): #open or close check self.flag = 1 #if checked COM open, set flag. print(self.instance[i].port + ":open") else: self.flag = 0 #if checked COM close, clear flag. print(self.instance[i].port + ":close") else: if(self.instance[i].isOpen() == True): print(self.instance[i].port + ":open") else: print(self.instance[i].port + ":close") return self.flag @QtCore.Slot() def debug_alert(self): print("call!") #QtQuick test function @QtCore.Slot(str) def ledred(self,arg1): for i in range(len(self.devices)): if (self.instance[i].port == arg1): packet = [] packet.append(0x00)#b packet.append(0x00)#g packet.append(0xff)#r self.instance[i].write(packet) @QtCore.Slot(str) def ledblue(self, arg1): for i in range(len(self.devices)): if (self.instance[i].port == arg1): packet = [] packet.append(0xff)#b packet.append(0x00)#g packet.append(0x00)#r print(packet) self.instance[i].write(packet) @QtCore.Slot(str) def ledgreen(self, arg1): for i in range(len(self.devices)): if (self.instance[i].port == arg1): packet = [] packet.append(0x00) packet.append(0xff) packet.append(0x00) self.instance[i].write(packet) @QtCore.Slot(str) def ledorange(self, arg1): for i in range(len(self.devices)): if (self.instance[i].port == arg1): packet = [] packet.append(0x00) packet.append(0x45) packet.append(0xff) self.instance[i].write(packet) @QtCore.Slot(str,int,int,int) def slider_changed(self, arg1,r,g,b): for i in range(len(self.devices)): if (self.instance[i].port == arg1): self.instance[i].write(b.to_bytes(1, 'big')) self.instance[i].write(g.to_bytes(1, 'big')) self.instance[i].write(r.to_bytes(1, 'big')) if __name__ == "__main__": os.environ["QT_QUICK_CONTROLS_STYLE"] = "Material" #set Material theme(QtQuick) app = QtWidgets.QApplication(sys.argv) myconnect = SerialComport() #Create instance engine = QtQml.QQmlApplicationEngine() #GUI ctx = engine.rootContext() ctx.setContextProperty("SerialComport", myconnect) #Connect GUi to SerialComport Class engine.load('mypyside2.qml') #qml file load. if not engine.rootObjects(): sys.exit(-1) sys.exit(app.exec_())
コード全文(qml側)
import QtQuick 2.12 import QtQuick.Window 2.12 import QtQuick.Controls 2.3 Window { function init(){ comupdate(); } function comupdate(){ comboBox.model.clear() var device = SerialComport.comlist(); for(var key in device){ comboBox.model.append({text:device[key]}); } comboBox.currentIndex = 1; } function part_visible(){ radioDelegate.visible = true radioDelegate1.visible = true radioDelegate2.visible = true radioDelegate3.visible = true slider.visible = true slider1.visible = true slider2.visible = true rectangle.visible = true } function part_hide(){ radioDelegate.visible = false radioDelegate1.visible = false radioDelegate2.visible = false radioDelegate3.visible = false slider.visible = false slider1.visible = false slider2.visible = false rectangle.visible = false } objectName: "a" visible: true width: 640 height: 480 color: "#e2dde2" title: qsTr("Controller") Component.onCompleted:{ init(); } Button { id: button x: 255 y: 28 text: qsTr("COM ") font.pointSize: 13 onClicked:{ comupdate(); } } ComboBox { id: comboBox x: 25 y: 28 width: 198 height: 40 visible: true editable: false model: ListModel { id: model } onCurrentIndexChanged:{ var flag = SerialComport.comlistchanged(comboBox.model.get(comboBox.currentIndex).text); if(flag == 1){ part_visible(); } else{ part_hide(); } } } Button { id: button1 x: 369 y: 28 text: qsTr("OPEN") font.pointSize: 13 onClicked:{ SerialComport.comopen(comboBox.model.get(comboBox.currentIndex).text); part_visible(); } } Button { id: button2 x: 475 y: 28 text: qsTr("CLOSE") font.pointSize: 13 onClicked:{ SerialComport.comclose(comboBox.model.get(comboBox.currentIndex).text); part_hide(); } } RadioDelegate { id: radioDelegate visible:true x: 81 y: 106 text: qsTr("Red") onClicked:{ SerialComport.ledred(comboBox.model.get(comboBox.currentIndex).text) } } RadioDelegate { id: radioDelegate1 visible:true x: 187 y: 106 text: qsTr("Blue") onClicked:{ SerialComport.ledblue(comboBox.model.get(comboBox.currentIndex).text) } } RadioDelegate { id: radioDelegate2 visible:true x: 293 y: 106 text: qsTr("Green") onClicked:{ SerialComport.ledgreen(comboBox.model.get(comboBox.currentIndex).text) } } RadioDelegate { id: radioDelegate3 x: 420 y: 106 text: qsTr("Orange") visible: true onClicked:{ SerialComport.ledorange(comboBox.model.get(comboBox.currentIndex).text) } } Slider { id: slider visible:true x: 81 y: 215 width: 439 height: 40 stepSize: 1 wheelEnabled: true to: 255 font.family: "Courier" font.capitalization: Font.AllLowercase value: 50 onValueChanged:{ SerialComport.slider_changed(comboBox.model.get(comboBox.currentIndex).text,slider.value,slider1.value,slider2.value) rectangle.color = Qt.rgba(slider.value/slider.to,slider1.value/slider.to,slider2.value/slider.to,1) } } Slider { id: slider1 visible:true x: 81 y: 261 width: 439 height: 40 stepSize: 1 wheelEnabled: true to: 255 value: 50 onValueChanged:{ SerialComport.slider_changed(comboBox.model.get(comboBox.currentIndex).text,slider.value,slider1.value,slider2.value) rectangle.color = Qt.rgba(slider.value/slider.to,slider1.value/slider.to,slider2.value/slider.to,1) } } Slider { id: slider2 visible:true x: 81 y: 307 width: 439 height: 40 stepSize: 1 wheelEnabled: true to: 255 value: 50 onValueChanged:{ SerialComport.slider_changed(comboBox.model.get(comboBox.currentIndex).text,slider.value,slider1.value,slider2.value) rectangle.color = Qt.rgba(slider.value/slider.to,slider1.value/slider.to,slider2.value/slider.to,1) } } Rectangle { id: rectangle visible:true x: 541 y: 261 width: 56 height: 46 color:Qt.rgba(0,0,0,1) } }