PySide2でQtQuick(qml)使うメモ3

今回はPySide2とPySerialを組み合わせて使うメモ

やることとしてはQMLで作成したGUI側でイベントを発生させてシリアルで何か送信してマイコンを制御するという感じ
つくったのはこんな感じのやつ

f:id:gsmcustomeffects:20191005005003p:plain

COMの選択をしてオープンをすると5~7のオブジェクトが表示される仕組み クローズすると消えるようになってる

f:id:gsmcustomeffects:20191005010331p:plain

コード全文は下に貼るのでちょい特殊な部分だけ解説していく流れで行きます。

尚基礎に関しては一番下に過去の記事貼っていますのでそちらを参考に環境構築などして下さい

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のドキュメントちゃんと読んでください。
最後に動作例の動画でも貼っときます


マイコン側参考コード

ハードは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)
    }
}