【仕事効率化】【PySide6】2つのPC間でシリアル通信、それを利用してマクロパッドを作る

はじめに

先日、Arduino Leonardoを用いて使っていない 2in1 PCでマクロパッドを作成しました。



使っていない 2in1 PC(またはタブレット)を有効活用、マクロパッドとして復活させる
【改良1】使っていない 2in1 PC(またはタブレット)を有効活用
【改良2】使っていない 2in1 PC(またはタブレット)を有効活用
【改良3】使っていない 2in1 PC(またはタブレット)を有効活用

前システムの概略


2in1 PCとArduino LeonardoがUSB接続のキーボード(マクロパッド)として機能します。


メインPCにはアプリのインストールなどの事前準備が全く必要ないことがメリットとして挙げられます。

前システムに対する考察

作ったマクロパッドは非常に便利で実際に使っています。

でも使っていて気付いたことがあります。



Arduino Leonardoいる?



メインPCにPythonがインストール可能である、USBシリアル変換アダプターのドライバーがインストール可能である、などがクリアできればArduino Leonardoが行っている信号の変換はメインPCのPythonで行えばいいのでは?だんだんそういう気持ちになってきました。


それができればArduino Leonardoは不要になります。


Bluetoothを使った無線シリアル通信であればUSBシリアル変換アダプターのドライバーインストールは不要です。

新システム

前置きが長くなってしまいました。

さっそく2つのPCで直接シリアル通信する新システムを作っていきましょう。

接続

有線接続と無線接続(Bluetooth)の2つの方法を示します。

有線接続

USBシリアル変換アダプターを使用しました。それぞれのPCで必要になるので2つ必要になります。

Switch Scienceから以下を2つ購入しました。これでなければいけない理由はありません。
FTDI USBシリアル変換アダプター(5V/3.3V切り替え機能付き)www.switch-science.com
それぞれのPCにドライバをインストールして接続します。


接続は3本のみです。
RXとTX
TXとRX
GNDとGND

無線接続

Bluetoothを使用します。両PCにBluetoothがあってPythonがインストールされている前提です。

準備するものはPC以外にありません。

やってみるとボタンをクリックしてから相手PCが反応するまで少しの遅延が存在します。

とりあえず試したい人は無線で実験してみるのもいいと思います。そこで感じた遅延は有線接続にすると改善する可能性があります。

Bluetooth接続の確立はやや難しかったので別に記事を書きました。
touch-sp.hatenablog.com

Pythonスクリプト

マクロパッド側

ボタンクリックを検知してシリアル信号を送信するだけです。

前システムから変更する必要がないのでそのまま使用しました。(こちらを参照)

メインPC側

受け取ったシリアル信号をどのように変換するか?

ちょうどいいパッケージが存在しました。「PyAutoGUI」です。
ちょうどいいパッケージが存在しました。「keyboard」です。

pip install pyside6==6.4.1
pip install keyboard==0.13.5
pip install pyperclip-1.8.2



今回のような状況に限れば「PyAutoGUI」より「keyboard」のほうが優れています。
touch-sp.hatenablog.com


スクリプトは2つに分けました。「main.py」と「action_list.py」としています。

main.py

from PySide6 import QtSerialPort
from PySide6.QtCore import Qt, QIODevice
from PySide6.QtWidgets import QApplication, QMainWindow, QWidget, QLabel, QComboBox, QPushButton, QVBoxLayout

from action_list import action_do

class Window(QMainWindow):

    serial = QtSerialPort.QSerialPort()

    def __init__(self):
        super().__init__()
        self.initUI()
        
    def initUI(self):
        
        self.connectLabel = QLabel()
        self.connectLabel.setAlignment(Qt.AlignCenter)

        self.connectBtn = QPushButton('connect')
        self.connectBtn.clicked.connect(self.pushBtn)

        self.port_selector = QComboBox()
        self.port_list = QtSerialPort.QSerialPortInfo.availablePorts()
        for each_port in self.port_list:
            self.port_selector.addItem(f'{each_port.portName()}: {each_port.description()}')

        layout = QVBoxLayout()
        layout.addWidget(self.port_selector)
        layout.addWidget(self.connectBtn)
        layout.addWidget(self.connectLabel)

        main = QWidget()
        main.setLayout(layout)

        self.setCentralWidget(main)
            
    def pushBtn(self):
        list_index = self.port_selector.currentIndex()
        self.serial.setPort(self.port_list[list_index])
        self.serial.setBaudRate(QtSerialPort.QSerialPort.Baud9600)
        self.serial.readyRead.connect(self.receive)
        self.serial.open(QIODevice.ReadOnly)

        self.connectLabel.setText('receiving serial')
        self.connectBtn.setEnabled(False)

    def closeEvent(self, e):
       self.serial.close()
       e.accept()
    
    def receive(self):
        data_from_PC = self.serial.read(1)
        action_do(int.from_bytes(data_from_PC, 'big'))       
        
if __name__ == "__main__":
    app = QApplication([])
    ex =Window()
    ex.show()
    app.exec()

action_list.py

import keyboard as kb

def action_do(x: int) -> None:
    match x:
        #PowerPoint settings
        case 0:
            kb.send('alt, n, x, h')
        case 1:
            kb.send('alt, n, p, d')
        case 2:
            kb.send('alt, n, s, h')
        case 3:
            kb.send('alt, h, g, a, t')
        case 4:
            kb.send('alt, h, g, a, l')
        case 5:
            kb.send('alt, h, g, a, r')
        case 6:
            kb.send('alt, h, g, a, b')
        case 7:
            kb.send('alt, h, g, a, c')
        case 8:
            kb.send('alt, h, g, a, m')
        case 9:
            kb.send('alt, h, g, a, h')
        case 10:
            kb.send('alt, h, g, a, v')

        #YouTube Settings
        case 20:
            kb.send('j')
        case 21:
            kb.send('l')
        case 22:
            kb.send('k')
        case 23:
            kb.send('f')

        #Keyboard settings
        case 40:
            kb.write('<h2></h2>')
            kb.send('left, left, left, left, left')
        case 41:
            kb.write('<h3></h3>')
            kb.send('left, left, left, left, left')
        case 42:
            kb.write('<h4></h4>')
            kb.send('left, left, left, left, left')
        case 43:
            kb.write('</br>')
        case 44:
            kb.write('</br></br>')
        case 45:
            kb.write('<a href=""></a>')
            kb.send('left, left, left, left, left, left')
        case 46:
            kb.write('python -m pip install --upgrade pip')
        case 47:
            kb.write('--cache-dir python_cache')
        case 48:
            kb.write('pip install torch torchvision --extra-index-url https://download.pytorch.org/whl/cu113')
        case 49:
            kb.write('ghp_m4h3yfj73luWkuyMV9oVX4Iwge06C8EV0uy')



main.pyを実行すると以下のようなものが出てきます。

COMポートを選択してボタンを押すとシリアル通信が開始されます。


このアプリは通信開始後アクティブ化されている必要はありません。最小化しても問題ありません。閉じない限り通信は継続しています。

最後に

Arduinoを使わないシステムが出来ました。

前システムではボタンを追加、変更するたびにArduino Leonardoに書き込む必要がありそれが地味に面倒くさかったです。

新システムではPythonスクリプトを変更するだけで済みます。