今回はPySide2とPySerialを組み合わせて使うメモ
やることとしてはQMLで作成したGUI側でイベントを発生させてシリアルで何か送信してマイコンを制御するという感じ
つくったのはこんな感じのやつ
COMの選択をしてオープンをすると5~7のオブジェクトが表示される仕組み クローズすると消えるようになってる
コード全文は下に貼るのでちょい特殊な部分だけ解説していく流れで行きます。
尚基礎に関しては一番下に過去の記事貼っていますのでそちらを参考に環境構築などして下さい
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()
for i in range(len(self.devices)):
self.instance.append(serial.Serial(port=self.devices[i],baudrate=9600))
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のドキュメントちゃんと読んでください。
最後に動作例の動画でも貼っときます
ハードはKinetis FRDM-K22FでソフトはMbedを利用して書いてます。
#include "mbed.h"
#include "stats_report.h"
PwmOut led1(LED1);
PwmOut led2(LED2);
PwmOut led3(LED3);
#define SLEEP_TIME 500
#define PRINT_AFTER_N_LOOPS 20
Serial pc(USBTX, USBRX);
int main()
{
SystemReport sys_state( SLEEP_TIME * PRINT_AFTER_N_LOOPS );
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;
led2 = 1.0-1.0/255.0f*(float)g;
led3 = 1.0-1.0/255.0f*(float)b;
}
}
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()
for i in range(len(self.devices)):
self.instance.append(serial.Serial(port=self.devices[i],baudrate=9600))
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()
@QtCore.Slot(str)
def comclose(self, arg1):
for i in range(len(self.devices)):
if (self.instance[i].port == arg1):
self.instance[i].close()
@QtCore.Slot(str,result='int')
def comlistchanged(self,arg1):
for i in range(len(self.devices)):
if(self.instance[i].port==arg1):
if(self.instance[i].isOpen() == True):
self.flag = 1
print(self.instance[i].port + ":open")
else:
self.flag = 0
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!")
@QtCore.Slot(str)
def ledred(self,arg1):
for i in range(len(self.devices)):
if (self.instance[i].port == arg1):
packet = []
packet.append(0x00)
packet.append(0x00)
packet.append(0xff)
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)
packet.append(0x00)
packet.append(0x00)
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"
app = QtWidgets.QApplication(sys.argv)
myconnect = SerialComport()
engine = QtQml.QQmlApplicationEngine()
ctx = engine.rootContext()
ctx.setContextProperty("SerialComport", myconnect)
engine.load('mypyside2.qml')
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)
}
}