Python 实现上位机(二)

目的

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

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

本章将用 Python 实现 Client 端。

简易的日志模块 

 利用 python 自带的 logging 模块实现 client 端的日志记录。日志保存路径为 C:\ProgramData\pyHost。

import logging
import datetime
import sys
import os

#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

如果日志目录不存在,则创建日志目录。每次运行的日志保存为 log_[日志创建的时间戳]。在保存日志到文件的同时,我们也希望在终端有日志打印,因此 logger 中添加了 fileHandler 和 consoleHandler。

Client (host) 端 API 

在 Client 端,我们希望能收发 dummyCom 正确的报文,并解析数据。dummyCom 规约见上一篇博文。

import socket
from traceback import format_exc
from dummyCom.dummyCom import dummyComStack, bprint

#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

在日志中引入 traceback.format_exc() 可以更好的记录代码运行过程中出现的报错。

hostAPI 初始化的时候,会创建一个 TCP 连接到 server 端。 其中 comParams 参数为 (ip, port)。

hostAPI 中的 parse, generate_message, 分别调用 dummyCom protocol stack 中定义的接口。

hostAPI 的 send_message,将给定的 bytes 发送到 server。

hosAPI 的 recv_message,从 TCP 连接收至多 1024 个 bytes。

运行与调试

先在 Terminal 中启动 Server 端

cd ./dummyCom
python ./dummyCom.py

再另启一个 Terminal,运行 Python 命令行,调用 hostAPI 命令:

python

首先创建一个 client 连接到 127.0.0.1:2333。我们想读取地址为 15 存储的数值,因此先生成一个 readMsg,读 (FC=0x03) 地址 15。将 readMsg 发送给 Server 之后,再读取收到的报文。从 logger 记录的 receive message: 23 83 00 0F 00 0F 91 32 可以看到 地址 15 存储的数值为 15。 

在 C:\ProgramData\pyHost 下查看 log:

下一篇将通过 PyQt5 实现 hostAPI 的 GUI。

附:hostAPI (without GUI)源码

'''
@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 traceback import format_exc
from dummyCom.dummyCom import dummyComStack, bprint

#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

  • 36
    点赞
  • 22
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值