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)
    }
}

PySide2でQtQuick(qml)使うメモ2

前回に引き続き今回もPySide2+Qtquick(qml)のメモ

GUIだと結構定番の電卓っぽい奴の実装

f:id:gsmcustomeffects:20190818201337g:plain


Button押下でイベント発生させてTextInputからデータもらってTextInputに返すサンプルだと思ってくれればいい

Pythonコード

import sys
import os
from PySide2 import QtCore, QtWidgets, QtQml

class Connect(QtCore.QObject):
    def __init__(self, parent=None):
        super(Connect, self).__init__(parent)

    @QtCore.Slot(int,int,result = float)
    def sum(self,arg1,arg2):
        return arg1 + arg2

    @QtCore.Slot(int, int, result=float)
    def sub(self, arg1, arg2):
        return arg1 - arg2

    @QtCore.Slot(int, int, result=float)
    def mul(self, arg1, arg2):
        return arg1 * arg2

    @QtCore.Slot(int, int, result=float)
    def div(self, arg1, arg2):
        return arg1 / arg2

if __name__ == "__main__":
    os.environ["QT_QUICK_CONTROLS_STYLE"] = "Material"
    app = QtWidgets.QApplication(sys.argv)
    myconnect = Connect()
    engine = QtQml.QQmlApplicationEngine()
    ctx = engine.rootContext()
    ctx.setContextProperty("Connect", 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
import QtQuick.Controls.Material 2.0

Window {
    objectName: "a"
    visible: true
    width: 640
    height: 480
    title: qsTr("Hello World")

    TextInput {
        id: textInput1
        x: 104
        y: 108
        width: 80
        height: 37
        text: qsTr("12")
        font.pixelSize: 20
    }

    TextInput {

        id: textInput2
        x: 179
        y: 108
        width: 80
        height: 37
        text: qsTr("2")
        font.pixelSize: 20
    }

    TextInput {

        id: textInput3
        x: 270
        y: 108
        width: 80
        height: 37
        text: qsTr("1")
        font.pixelSize: 20
    }

    Row {
        id: row
        x: 104
        y: 186
        width: 427
        height: 119
        spacing: 9

        Button {
            objectName:"button"
            id: button1
            width: 100
            height: 50
            text: qsTr("add")
            font.pointSize: 14
            onClicked:function(){
                textInput3.text = Connect.sum(textInput1.text,textInput2.text)
            }
        }

        Button {
            id: button2
            width: 100
            height: 50
            text: qsTr("sub")
            font.pointSize: 13
            objectName: "button"
            onClicked:function(){
                textInput3.text = Connect.sub(textInput1.text,textInput2.text)
            }
        }

        Button {
            id: button3
            width: 100
            height: 50
            text: qsTr("mul")
            font.pointSize: 12
            objectName: "button"
            onClicked:function(){
                textInput3.text = Connect.mul(textInput1.text,textInput2.text)
            }
        }

        Button {
            id: button4
            width: 100
            height: 50
            text: qsTr("div")
            font.pointSize: 12
            objectName: "button"
            onClicked:function(){
                textInput3.text = Connect.div(textInput1.text,textInput2.text)
            }
        }
    }
}

解説的なやつ

python

@QtCore.Slot(int,int,result = float)
    def sum(self,arg1,arg2):
        return arg1 + arg2

addの例で解説する。
@QtCore.Slot(int,int,result = float)で引数の型と返り値の型をqmlに対して公開する。
あとはdefで関数を実装するだけ前回との違いはSlotでreturnしているとこ。
ここで注意が必要なのが書かなくてもエラーは出ないがresult = floatは必須ということ
書かないと何も値が返ってこない

qml側

TextInput {

        id: textInput3
        x: 270
        y: 108
        width: 80
        height: 37
        text: qsTr("1")
        font.pixelSize: 20
    }

Button {
            objectName:"button"
            id: button1
            width: 100
            height: 50
            text: qsTr("add")
            font.pointSize: 14
            onClicked:function(){
                textInput3.text = Connect.sum(textInput1.text,textInput2.text)
            }
        }

こちらもaddの例で説明する。

まずqmlオブジェクトに対してはtextInput3.textのようにid.propertyでアクセスできる。(qmlではオブジェクト以下のパラメータ類のことをPropertyと呼ぶ

次にonclicked部分で押下時の動作を定義している。
textInput3.textにpython側のsumの返り値を代入するということをしているだけだ。

あくまでqmlオブジェクトへの代入はqml側でやるというとこがQtの方針
PythonC++側からも子オブジェクトにアクセスできるがQt公式は推奨していない(参照関係がごちゃごちゃになるため

この関係を守るとUI設計者と内部実装側が完全分業可能なので合理的ではある。

PySide2でQtQuick(qml)使うメモ1

自分用のメモです。

自分の動作環境は

  • PyCharm 無料版
  • Python 3.7.4 or 3.6(仮想で両方で試した)
  • Pycharm内臓のVenvでパッケージ管理

導入

PythonでQtを扱うにはPyQtとPysideの2つがある。

verごとに書くとこんな感じ

  • Qt4 : PyQt4,PyiSde
  • Qt5 : PyQt5,PySide2

という感じになる。
現行で使うならQt5系を利用するほうがいいだろうということでPyQt5,PySide2を選ぶことになる。
選定にあたりいろいろググった結果PySide2ってのがQt公式がサポートするQt5バインディング(Qt for Pythonと呼ばれてるのがこれにあたる)らしいのでこれを使うことにする。

ui -> pyをする使い方

QtDesignerというソフトを使ってグラフィカルにUIをデザインしてそのファイル(.ui)をpyside2-uic.exeを使って.py拡張子ファイルに変換してインポートする手法のこと。
この方法が一番情報も多くて使いやすいと思う。

いろんな人が情報を公開しているのでその辺を読むといいだろう
note.mu

qiita.com

UIファイルの更新のたびにPyCharm側でpyside2-uic.exeを自動で呼び出すこともできる。
qiita.com

QtQuickについて

これについては公式の説明が分かりやすい
Qt Quick 入門 第1回: Qt Quick とは - Qt Japanese Blog

ようはuiファイルではなくqmlというjson風の表記を使ってUIをデザインしていく手法であり比較的楽にかっこいいUIを作成できる。

f:id:gsmcustomeffects:20190818004444p:plain

画像は私が作成したUIであるが5分ぐらいでこのぐらいのGUIを作成できる。

PySide2導入とテスト

早速本題に入る。
まずはPyCharmで新規プロジェクトをつくる。
f:id:gsmcustomeffects:20190818010410p:plain

上部メニューのFile/settingでこの画面が開くのでproject interpreterの画面まで持ってくる。
f:id:gsmcustomeffects:20190818010741p:plain

+ボタンをクリックして必要なモジュールをインストールしてくる。
ここではPySide2を検索して持ってくる。
f:id:gsmcustomeffects:20190818011621p:plain

インストールができたら.pyファイルを作成して以下のリンクのコードを実行してみる。
https://doc.qt.io/qtforpython/tutorials/basictutorial/dialog.html

このような画面が出ていればPySide2の導入はOK
f:id:gsmcustomeffects:20190818012746p:plain

PySide2でQtQuick(qml)ファイルを扱う

次にqmlファイルを作るためにQt Creatorをダウンロードしてくる。
Download Qt: Choose commercial or open source

アカウントとか聞かれますけどスキップで何とかなります。
適当に自分の欲しいものをインストールしてください

CreatorとQt5.x系があればOKです。

Qt Creatorをインストール出来たら開いて新規プロジェクトでUI Prototypeを選ぶ
f:id:gsmcustomeffects:20190818021459p:plain

キットに関しては適当に選ぶといいです。
するとこのような画面になるのでデザインをクリックします。
f:id:gsmcustomeffects:20190818021603p:plain

UIデザイン画面が開くので次にインポートタブを開きます。
f:id:gsmcustomeffects:20190818022028p:plain

QtQuick.ControlsとQtQuick.Controls.Materialsを追加
f:id:gsmcustomeffects:20190818022236p:plain

そうするとエレメントタブにボタン類が入ってくる
f:id:gsmcustomeffects:20190818022427p:plain

ボタンをドラッグしてUIに追加する。
f:id:gsmcustomeffects:20190818022542p:plain

次にできたqmlをPycharmプロジェクトに入れてあげる
f:id:gsmcustomeffects:20190818022837p:plain

そしたらこのコードを実行

import sys
from PySide2.QtGui import QGuiApplication
from PySide2.QtQml import QQmlApplicationEngine
from PySide2.QtCore import QUrl

if __name__ == '__main__':
    app = QGuiApplication(sys.argv)
    engine = QQmlApplicationEngine()
    engine.load(QUrl("mypyside2.qml"))

    if not engine.rootObjects():
        sys.exit(-1)

    sys.exit(app.exec_())

先ほど作ったUIが表示できていればOK
f:id:gsmcustomeffects:20190818023149p:plain

ボタンへのイベント追加

次に配置したウィジェットPythonコードをつないでいく。

python code

import sys
import os
from PySide2 import QtCore, QtWidgets, QtQml

class Connect(QtCore.QObject):
    def __init__(self, parent=None):
        super(Connect, self).__init__(parent)

    @QtCore.Slot()
    def button_clicked(self):
        print("button clicked")



if __name__ == "__main__":
    os.environ["QT_QUICK_CONTROLS_STYLE"] = "Material"
    app = QtWidgets.QApplication(sys.argv)
    myconnect = Connect()
    engine = QtQml.QQmlApplicationEngine()
    ctx = engine.rootContext()
    ctx.setContextProperty("Connect", myconnect)
    engine.load('mypyside2.qml')
    if not engine.rootObjects():
        sys.exit(-1)

    sys.exit(app.exec_())

qml code

import QtQuick 2.12
import QtQuick.Window 2.12
import QtQuick.Controls 2.3
import QtQuick.Controls.Material 2.0

Window {
    visible: true
    width: 640
    height: 480
    title: qsTr("Hello World")

    Button {
        id: button
        x: 270
        y: 220
        text: qsTr("Button")
        onClicked:Connect.button_clicked()//python側のクラスとつなぐ
    }
}


実行してボタンをクリックするとprint文が実行されるはず。
f:id:gsmcustomeffects:20190818025353p:plain

説明(Python側)

まずQtCore.QObjectを継承したクラス(Connect)を作成する。
@QtCore.Slotでqml側にメソッドの存在を通知する。これによりqml側でdef以下を呼べるようになる。
@表記はPythont的に言うとデコレータってやつらしい。

os.environ["QT_QUICK_CONTROLS_STYLE"] = "Material"

Materialテーマを有効にする記述で特に必要はない
有効にしていると以下のリンクのようなことができる。
https://doc.qt.io/qt-5/qtquickcontrols2-material.html

ctx = engine.rootContext()
ctx.setContextProperty("Connect", myconnect)

engine内部の子ウィジェットを検索可能にする記述(あってるかわかんないけど
QMLにアクセスしたりQMLからアクセスしたりする場合はこの二行が必須っぽい

説明(qml側)

説明するといってもonClickedぐらい

クリックされた時の動作を記述するメンバー
ウィジェットごとにイベントが用意されてるSliderならonValueChangedみたいな感じで書ける。
Ctrl+Spaceで補完できるので何があるかは分かると思う。

メンバーにカーソルを合わせてF1を押すとウィジェットの説明を表示できる
f:id:gsmcustomeffects:20190818031331p:plain


参考文献

rootcontextの記述はC++も同様なので参考になった。
その他qmlとの相互イベント通知の参考になる。
QMLとC++のバインディング - Qiita

今回の記事はこの問題の劣化版みたいなものなのでこっちのスレッド読むともっといいのがつくれると思う
Connect python signal to QML ui slot with PySide2 - Stack Overflow

公式のサンプルとチュートリアル
QMLの例が2個しかないけど書き方の参考にはなる。
https://doc.qt.io/qtforpython/tutorials/index.html

CMSIS DSP とコンパイル最適化オプション



色々試してたらGCCの場合最適化しないとFPU命令呼んでくれないっぽい
f:id:gsmcustomeffects:20190207231605p:plain

optimize入れるとちゃんと呼ばれてる
f:id:gsmcustomeffects:20190207231530p:plain

i.MX RT1020(CortrexM7 DP FPU)のSQRTしか試してないのであれですが入れとくに越したことはないかと。
尚、消されたくない変数にはvolatileを付けましょう。

以上

Python:スペクトログラムの描画



今回は信号の時系列変化を可視化できるスペクトログラムについてです。
使うメソッドはPython:scipy.signal.spectrogramです。

オプションパラメータがたくさんありますが

f,t,Sxx = signal.spectrogram(data, fs, nperseg=512)

のように書いてあげればいいです。

spectrogramの描画

今回は前回作成したSwept-sine信号のスペクトラムの描画をやってみたいと思う
信号の生成は前回の記事を参考にしてほしい
gsmcustomeffects.hatenablog.com

今回やることとしてはsignal.spectrogramメソッド呼ぶだけ

import matplotlib.pylab as plt
import numpy as np
import scipy.signal as signal

f1 = 10        # start frequency
f2 = 12000       # end frequency
fs = 96000      # sampling frequency
T = 10          # time duration of the sweep
fade = [48000, 480]   # samlpes to fade-in and fade-out the input signal


'''generation of the signal'''
L = T/np.log(f2/f1)                    # parametre of exp.swept-sine
t = np.arange(0,np.round(fs*T-1)/fs,1/fs)  # time axis
s = np.sin(2*np.pi*f1*L*np.exp(t/L))       # generated swept-sine signal

# fade-in the input signal
if fade[0]>0:
    s[0:fade[0]] = s[0:fade[0]] * ((-np.cos(np.arange(fade[0])/fade[0]*np.pi)+1) / 2)

# fade-out the input signal
if fade[1]>0:
    s[-fade[1]:] = s[-fade[1]:] *  ((np.cos(np.arange(fade[1])/fade[1]*np.pi)+1) / 2)
    
f,t,Sxx = signal.spectrogram(s, fs, nperseg=512)

plt.figure()
plt.pcolormesh(t,f,Sxx,cmap="GnBu")
plt.xlim([0,10])
plt.ylim([0,10000])
plt.xlabel("Time [sec]")
plt.ylabel("Freq [Hz]")
plt.colorbar()
plt.show()

matplotのcmapの設定で好みの色に設定できる


f:id:gsmcustomeffects:20190117192936p:plain
Swept-sine信号のスペクトログラム
f:id:gsmcustomeffects:20190117193200p:plain
色変更後[cmap="Spectral"]

Python:ボード線図の描画



今回はScipyのsignal.bodeを使って周波数応答を描画してみる。
使うメソッドは

  • scipy.signal.lti
  • scipy.signal.bode

である。

scipy.signal.lti

線形時不変システムを作成できるメソッド。引数の渡し方によって伝達関数だったり状態空間モデルとかいろいろ定義できる。
詳しくは公式ドキュメントが参考になる。
scipy.signal.lti — SciPy v1.8.0 Manual

G(s) = \frac{10000}{s+10000}

というのを作りたかったら

from scipy import signal

# transferfunc = 10000 / s+10000
s = signal.lti([10000], [1.0,10000])

とすれば良い

scipy.signal.bode

次にボード線図の方だ
これもドキュメント読んでもらえればわかるんだけど(scipy.signal.bode — SciPy v1.8.0 Manual

f:id:gsmcustomeffects:20190113112621p:plain
bodeメソッドの引数、返り値

基本的にLTIで作ったインスタンスを入れてあげればw,mag,phaseが帰ってくる感じ.
見た感じARMAモデルそのまま打ち込んでも表示してくれるみたい

w[radian/sec]はグラフのx軸に当たる部分で正規化周波数で帰って来るのでfにしたい場合は2piで割れば良い

実際の使い方はこうなる

w, mag, phase = signal.bode(s)

bode plot

最後に全体のコードを張っておく

import numpy as np
from scipy import signal
import matplotlib.pyplot as plt

# transferfunc = 10000 / s+10000
s = signal.lti([10000], [1.0,10000])
w, mag, phase = signal.bode(s)

f = w/(2.0*np.pi)
fig, (ax0, ax1) = plt.subplots(nrows=2, sharex=True)
ax0.semilogx(f, mag, 'b-')
ax0.set_ylabel("Magnitude[dB]")
ax1.semilogx(f, phase, 'r-')
ax1.set_ylabel("Phase[degree]")
ax1.set_xlabel("Freq[Hz]")
plt.show()

f:id:gsmcustomeffects:20190113113807p:plain
bode plot