Python 实现上位机(三)

目的

记录并分享一个用 python 实现上位机功能的思路与模板。将通过三个方面分享: 

  1. Python 自定义简易通信规约,并实现 Server 端
  2. Python 实现 Client 端
  3. PyQt5 绘制上位机界面,通过上位机作为 Client 访问 Server 端

本章将用 PyQt5 实现上位机 GUI。

PyQt5Designer 绘制 UI

上位机通过 IP 地址以及 TCP 端口号连接到 Server 端。

IP 地址和 TCP 端口号分别用 QLineEdit 输入,连接按钮为一个 QPushButton。

连接成功之后,通过 GUI 发送 读/写 命令到指定的地址。 

读/写命令通过 QComboBox 选择。地址,数据通过 QLineEdit 输入。读的结果显示在一个 read-only 的 QLineEdit 中。发送按钮为一个 QPushButton。

pyuic5 工具将 ui 文件转换为 python code

ui 文件保存为 host.ui。通过 pyuic5 工具将 host.ui 转换为 ui_host.py。打开一个 Terminal 运行:

pyuic5 -o ui_host.py host.ui

ui 文件中的 main-window 对应生成为一个名为 Ui_MainWindow 的类。main-window 下的每一个 widget 都对应 Ui_MainWindow Class 下的一个成员。

python 完善 GUI 后端功能

from PyQt5 import QtWidgets
from ui_host import Ui_MainWindow

#region hostUI
class hostUI(QtWidgets.QMainWindow, Ui_MainWindow):
    def __init__(self):
        QtWidgets.QMainWindow.__init__(self)
        self.setupUi(self)
        self.host = None
        # init
        self.setBaseSize(1000,500)
        self.cmd_change()
        self.resultLine.setEnabled(False)
        # slots
        self.connectButton.clicked.connect(self.connect_to_server)
        self.sendButton.clicked.connect(self.send_to_server)
        self.commandList.currentIndexChanged.connect(self.cmd_change)

    def connect_to_server(self):
        # connect to dummy server
        try:
            if self.connectButton.text().upper()=='CONNECT':
                ip = self.serverIp.text()
                port = int(self.serverPort.text())
                self.host = hostAPI(ip, port)
                self.connectButton.setText("disconnect")
            else:
                self.host.channel.close()
                self.connectButton.setText("connect")
        except Exception as err:
            logger.debug(f'[error   :] {err}')
            logger.debug(f'\n[ERR INFO:]\n{format_exc()}\n[END OF ERROR]\n')
    
    def send_to_server(self):
        # send message to dummy server
        try:
            cmd = self.commandList.currentText()
            # check read or write
            fc = 0x03 if cmd.upper()=='READ' else 0x06
            addr = int(self.address.text())
            # read message, data line is disabled
            data = 0 if fc==0x03 else int(self.dataLine.text())
            sMsg = self.host.generate_message(fc, addr, data)
            self.host.send_message(sMsg)
            rMsg = self.host.recv_message()
            if rMsg is None:
                raise Exception("Received None from host")
            _, _, res = self.host.parse(rMsg)
            self.resultLine.setText(str(res))
        except Exception as err:
            logger.debug(f'[error   :] {err}')
            logger.debug(f'\n[ERR INFO:]\n{format_exc()}\n[END OF ERROR]\n')

    def cmd_change(self):
        self.dataLine.setText('')
        self.resultLine.setText('')
        if self.commandList.currentText().upper()=='READ':
            self.dataLine.setEnabled(False)
        elif self.commandList.currentText().upper()=='WRITE':
            self.dataLine.setEnabled(True)
        else:
            logger.debug('unknown command text')

#endregion

主入口 __name__ == '__main__':

if __name__ == '__main__':
    # launch host UI
    app = QtWidgets.QApplication(sys.argv)
    win = hostUI()
    win.show()
    sys.exit(app.exec_())

运行与调试

首先启动 server 端:

cd ./dummyCom
python ./dummyCom.py

运行 Client 端 GUI:

python host.py

运行结果: 

连接到 127.0.0.1:2333

 

读取地址 120 数据 

写地址 120 数据为 240

再次读取 120 地址数据。数据变为 240,写入成功。

 

 附:host.py 完整源码:

'''
@auther:    echen.hu
@email:     echen.hu@163.com

This script defines a template for a host machine, which
can communicate with client machines through some kind of
protocol over ethernet, serial etc.
'''
import logging
import datetime
import socket
import sys
import os
from time import sleep
from traceback import format_exc
from dummyCom.dummyCom import dummyComStack, bprint
from PyQt5 import QtWidgets
from ui_host import Ui_MainWindow

#region LOGGING
LOGPATH = 'C:\\ProgramData\\pyHost'

if not os.path.exists(LOGPATH):
    os.makedirs(LOGPATH)

timeStamp = datetime.datetime.strftime(datetime.datetime.now(), '%Y_%m_%d_%H_%M_%S') 

logger = logging.getLogger(__name__)  
logger.setLevel(logging.DEBUG)
fileHandler = logging.FileHandler(f"{LOGPATH}\\log_{timeStamp}.log")
consoleHandler = logging.StreamHandler(sys.stdout)
formatter = logging.Formatter('%(asctime)s : %(name)s  : %(funcName)s : %(levelname)s : %(message)s')
fileHandler.setFormatter(formatter)
consoleHandler.setFormatter(formatter)
logger.addHandler(fileHandler)
logger.addHandler(consoleHandler)
#endregion

#region HOSTAPI
class hostAPI(object):
    def __init__(self, *comParams):
        # define a communication channel
        # TCP communication is taken in the template
        self.channel = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
        self.channel.settimeout(3)
        self.stack = dummyComStack()
        try:
            self.channel.connect(comParams)
            logger.debug(f'HOST connected: {comParams}')
        except Exception as err:
            logger.debug(f'HOST cannot connect: {comParams}')
            logger.debug(f'Exeption: {err}')
            logger.debug(f'{format_exc}')
            self.channel = None

    def parse(self, msg: bytes):
        '''
        @params message
        @return function_code, address, data
        '''
        return self.stack.parse_msg(msg)

    def generate_message(self, fc: int, addr: int, data: int):
        '''
        @params function_code, address, data
        @return message
        '''
        return self.stack.generate_msg(fc, addr, data)
    
    def send_message(self, msg: bytes):
        '''
        @params message
        @return send_res
        '''
        try:
            self.channel.sendall(msg) # type:ignore
            logger.debug(f'[send --->] {bprint(msg)}')
            return True
        except Exception as err:
            logger.debug(f'[error   :] {err}')
            logger.debug(f'\n[ERR INFO:]\n{format_exc()}\n[END OF ERROR]\n')
            return None

    def recv_message(self):
        '''
        @return message
        '''
        try:
            rMsg = self.channel.recv(1024)
            logger.debug(f'[recv <---] {bprint(rMsg)}')
            return rMsg # type:ignore
        except Exception as err:
            logger.debug(f'[error   :] {err}')
            logger.debug(f'\n[ERR INFO:]\n{format_exc()}\n[END OF ERROR]\n')
            return None
#endregion

#region hostUI
class hostUI(QtWidgets.QMainWindow, Ui_MainWindow):
    def __init__(self):
        QtWidgets.QMainWindow.__init__(self)
        self.setupUi(self)
        self.host = None
        # init
        self.setBaseSize(1000,500)
        self.cmd_change()
        self.resultLine.setEnabled(False)
        # slots
        self.connectButton.clicked.connect(self.connect_to_server)
        self.sendButton.clicked.connect(self.send_to_server)
        self.commandList.currentIndexChanged.connect(self.cmd_change)

    def connect_to_server(self):
        # connect to dummy server
        try:
            if self.connectButton.text().upper()=='CONNECT':
                ip = self.serverIp.text()
                port = int(self.serverPort.text())
                self.host = hostAPI(ip, port)
                self.connectButton.setText("disconnect")
            else:
                self.host.channel.close()
                self.connectButton.setText("connect")
        except Exception as err:
            logger.debug(f'[error   :] {err}')
            logger.debug(f'\n[ERR INFO:]\n{format_exc()}\n[END OF ERROR]\n')
    
    def send_to_server(self):
        # send message to dummy server
        try:
            cmd = self.commandList.currentText()
            # check read or write
            fc = 0x03 if cmd.upper()=='READ' else 0x06
            addr = int(self.address.text())
            # read message, data line is disabled
            data = 0 if fc==0x03 else int(self.dataLine.text())
            sMsg = self.host.generate_message(fc, addr, data)
            self.host.send_message(sMsg)
            rMsg = self.host.recv_message()
            if rMsg is None:
                raise Exception("Received None from host")
            _, _, res = self.host.parse(rMsg)
            self.resultLine.setText(str(res))
        except Exception as err:
            logger.debug(f'[error   :] {err}')
            logger.debug(f'\n[ERR INFO:]\n{format_exc()}\n[END OF ERROR]\n')

    def cmd_change(self):
        self.dataLine.setText('')
        self.resultLine.setText('')
        if self.commandList.currentText().upper()=='READ':
            self.dataLine.setEnabled(False)
        elif self.commandList.currentText().upper()=='WRITE':
            self.dataLine.setEnabled(True)
        else:
            logger.debug('unknown command text')

#endregion

if __name__ == '__main__':
    # launch host UI
    app = QtWidgets.QApplication(sys.argv)
    win = hostUI()
    win.show()
    sys.exit(app.exec_())

  • 10
    点赞
  • 14
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值