streamlit串口工具开发尝试

streamlit串口工具开发尝试

需求

编写一个串口工具,可按所需的格式生成测试指令,指令最后是校验位。

开发

开始用streamlit写发现串口对象只能在发送的时候创建、打开和关闭,一个函数里执行完成所有操作,觉得不好用,无法配置串口号之类的,于是就尝试把串口和界面分开,单独一个py文件处理串口逻辑一个py文件运行界面,该思路完全可行,只是中间需要用socket等方法进行数据通信,感觉把问题复杂化了,于是最终还是改成了strealit中开多线程的方式,界面与串口线程之间采用队列进行数据交互,最终功能也实现了,但是打开串口的时候老是卡一会,而且在命令行没法用Ctrl+C结束进程,反正感觉没有之前用pyqt写的好用,接收代码就没有继续写了,以下代码仅供参考。

python 复制代码
# -*- coding: utf-8 -*-

import time
import queue
import threading
import streamlit as st
import serial

st.set_page_config(
    page_title="Ex-stream-ly Cool App",
    page_icon="🧊",
    layout="wide",
    initial_sidebar_state="expanded",
    menu_items={
        'Get Help': 'https://www.extremelycoolapp.com/help',
        'Report a bug': "https://www.extremelycoolapp.com/bug",
        'About': "# This is a header. This is an *extremely* cool app!"
    }
)

# 界面线程发送给子线程的数据队列
if 'maintosub_queue' not in st.session_state:
    st.session_state.maintosub_queue = queue.Queue()
# 子线程发送给主线程的数据队列
if 'subtomain_queue' not in st.session_state:
    st.session_state.subtomain_queue = queue.Queue()
# 子线程停止事件
if "stop_event" not in st.session_state:
    st.session_state.stop_event = threading.Event()
# 子线程运行状态
if "thread_running" not in st.session_state:
    st.session_state.thread_running = False
# 界面最新数据
if "last_input_data" not in st.session_state:
    st.session_state.last_input_data = 5
# 收到新数据后触发rerun
if "need_rerun" not in st.session_state:
    st.session_state.need_rerun = False
serial.PARITY_NONE
# 子线程任务:实时接收界面数据并处理
def background_task(stop_event, maintosub_queue, subtomain_queue):
    config_value = maintosub_queue.get()
    ser=serial.Serial()    
    ser.port = config_value[0]  # 串口设备路径  
    ser.baudrate = config_value[1]          # 波特率  
    ser.bytesize = serial.EIGHTBITS  # 数据位长度为8位  
    ser.parity = config_value[2]   # 无奇偶校验  
    ser.stopbits = serial.STOPBITS_ONE  # 1位停止位  
    ser.timeout = 1             # 读取超时时间为1秒
    try:
        ser.open()
    except serial.SerialException:
        print("串口不存在或无法打开,请检查串口名称和权限")
    except PermissionError:
        print("没有权限访问串口,请检查用户权限")
    except Exception as e:
        print(f"打开串口时发生未知错误:{e}")
    if ser.is_open:
        while not stop_event.is_set():
            time.sleep(0.1)
            # 检查是否有新数据发送到子线程
            if not maintosub_queue.empty():
                # 取出最新数据
                current_value = maintosub_queue.get()
                ser.write(current_value)# 写入数据
                print(current_value)
            count = ser.inWaiting()
            if count > 0:
                Read_buffer=ser.read_all()     # 我们需要读取的是40个寄存器数据,即40个字节
                subtomain_queue.put(str(Read_buffer))
    ser.close()
    #subtomain_queue.put({'thread_running': False})


# 界面部分
st.title("主线程与子线程传递数据示例")

# 刷新页面时重置数据
st.session_state.need_rerun = True

r24c1,r24c2,r24c3 = st.columns(3)
with r24c1:
    uart_comNumber = st.selectbox("comNumber",("COM1","COM2","COM3","COM4","COM5","COM6","COM7","COM8","COM9"))
with r24c2:
    uart_band = st.selectbox("波特率",("115200","38400","921600"))
with r24c3:
    uart_verification = st.radio(label = "校验位", 
                   options = ("N","O","E"), 
                   index = 1, # 初始化选项
                   format_func = lambda x : f"{x}", #这是格式化函数,注意我把原始选项ABC修改了。一般地,跟字典或dataframe结合也很好用。
                   key = "radio_demo", 
                   help = "我是帮助文字", # 看到组件右上角的问号了没?上去悬停一下。
                   on_change = None, args = None, kwargs = None)
    
r25c1,r25c2,r25c3 = st.columns(3)
with r25c1:
    if st.button("打开串口", disabled=st.session_state.thread_running):
        st.session_state.thread_running = True
        st.session_state.stop_event.clear()
        st.session_state.maintosub_queue.queue.clear()
        st.session_state.maintosub_queue.put((uart_comNumber,uart_band,uart_verification))
        st.session_state.subtomain_queue.queue.clear()
        st.session_state.last_input_data = 5
        if "result" in st.session_state:
            del st.session_state.result
        thread = threading.Thread(
            target=background_task,
            args=(st.session_state.stop_event, 
                st.session_state.maintosub_queue, 
                st.session_state.subtomain_queue)
        )
        thread.start()
with r25c2:
    if st.button("关闭串口", disabled=not st.session_state.thread_running):
        st.session_state.stop_event.set()
with r25c3:
    # 显示线程状态
    if st.session_state.thread_running :
        st.image("green.ico")
    else:
        st.image("red.ico")

input_data = st.number_input("输入数值", value=5, min_value=5, max_value=100, step=1, key='input_data')

xmqmode = st.radio(label = "工作模式", 
                options = ("白天连续","白天频闪","夜间连续","夜间频闪"), 
                index = 1, # 初始化选项
                format_func = lambda x : f"{x}", #这是格式化函数,注意我把原始选项ABC修改了。一般地,跟字典或dataframe结合也很好用。
                key = "radio_demo1", 
                help = "我是帮助文字", # 看到组件右上角的问号了没?上去悬停一下。
                on_change = None, args = None, kwargs = None)
match  xmqmode:
    case "白天连续": xmqmode_by=0
    case "白天频闪": xmqmode_by=1
    case "夜间连续": xmqmode_by=2
    case "夜间频闪": xmqmode_by=3
checksum = (0x55 + 0xaa + xmqmode_by + st.session_state.last_input_data + 0x11) & 0xff
sendcmd = bytes([0x55, 0xaa, xmqmode_by, st.session_state.last_input_data, 0x11, checksum])

if st.session_state.thread_running and input_data != st.session_state.last_input_data:
    # 将新数据推送到队列(每次变化时触发)
    st.session_state.maintosub_queue.put(sendcmd)
    st.session_state.last_input_data = input_data

def button_clicked2():
    st.session_state.maintosub_queue.put(bytes([0x55, 0xaa, 0x00, 0x05, 0x00, 0x04]))
    print(bytes([0x55, 0xaa, 0x00, 0x05, 0x00, 0x04]))
st.button("关闭出光",on_click=button_clicked2)

# 等待子线程处理完数据
while st.session_state.thread_running:
    # 队列中有数据则处理完数据后重新刷新页面
    if not st.session_state.subtomain_queue.empty():
        # 处理子线程发送的最新数据
        rev_data = st.session_state.subtomain_queue.get()
        st.write(rev_data)
        print(rev_data)
    time.sleep(0.1)

# 刷新页面
if st.session_state.need_rerun:
    st.rerun()

多进程通信问题

用ai生成的多进程通信代码,反正都没通上,不清楚原因,用的是:

python 复制代码
# common_queue.py - 公共队列模块,所有独立进程共享此Queue
from multiprocessing import Manager
# 创建全局Queue实例,所有导入该模块的进程均使用此实例
manager = Manager()
comm_queue = manager.Queue(maxsize=0)  # maxsize指定队列最大容量,0为无限
python 复制代码
# process1.py - 独立进程1(无亲缘,生产者)
from common_queue import comm_queue
import time
import random

if __name__ == "__main__":
    print("进程1启动,开始向队列写入数据...")
    count = 0
    while True:
        count += 1
        data = {
            "type": "send",
            "content": f"测试数据{count}",
            "timestamp": time.time()
        }
        comm_queue.put(data)  # 入队,向公共队列写数据
        print(f"进程1写入:{data}")
        time.sleep(random.uniform(0.5, 1.5))  # 随机延迟,模拟实际业务
python 复制代码
# process2.py - 独立进程2(无亲缘,消费者)
from common_queue import comm_queue
import time

if __name__ == "__main__":
    print("进程2启动,开始从队列读取数据...")
    while True:
        if not comm_queue.empty():
            data = comm_queue.get()  # 出队,从公共队列读数据
            print(f"进程2读取:{data}")
        time.sleep(0.2)  # 轮询间隔,降低CPU占用

其他内容

过程中用AI写了一个socket的服务器和客户端程序,测试没问题,也贴出来,万一后面能用上,代码没写完,但是对于socket发送/接收的数据流,可以采用json进行数据格式化,主要就2个函数:

  • json.dumps 将 Python 对象编码成 JSON 字符串
  • json.loads 将已编码的 JSON 字符串解码为 Python 对象

客户端程序:

python 复制代码
import socket
import threading
import time

class TCPClient:
    def __init__(self, host='127.0.0.1', port=8888):
        self.host = host
        self.port = port
        self.client_socket = None
        self.running = False
        self.receive_thread = None
    
    def connect(self):
        """连接到TCP服务器"""
        try:
            # 创建socket对象
            self.client_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
            
            # 连接到服务器
            self.client_socket.connect((self.host, self.port))
            
            print(f"✅ 已连接到服务器 {self.host}:{self.port}")
            
            # 启动接收消息的线程
            self.running = True
            self.receive_thread = threading.Thread(target=self.receive_messages)
            self.receive_thread.daemon = True
            self.receive_thread.start()
            
        except ConnectionRefusedError:
            print(f"❌ 无法连接到服务器 {self.host}:{self.port}")
        except Exception as e:
            print(f"❌ 连接失败: {e}")
        finally:
            self.disconnect()
    
    def send_messages(self,message):
        """发送消息到服务器"""
        try:
            if  self.running:
                if message:
                    self.client_socket.send(message.encode('utf-8'))
        except Exception as e:
            print(f"❌ 发送消息时出错: {e}")
    
    def receive_messages(self):
        """接收服务器消息"""
        try:
            while self.running:
                # 接收服务器响应
                data = self.client_socket.recv(1024)
                if not data:
                    print("📭 服务器断开连接")
                    self.running = False
                    break

                # 打印服务器消息
                message = data.decode('utf-8')
                print(f"📥 服务器回复: {message}", end='')
                
        except ConnectionResetError:
            print("📭 连接被服务器重置")
            self.running = False
        except Exception as e:
            if self.running:  # 只在运行时报告错误
                print(f"❌ 接收消息时出错: {e}")
    
    def disconnect(self):
        """断开连接"""
        self.running = False
        if self.client_socket:
            self.client_socket.close()
            print("🔌 已断开与服务器的连接")

if __name__ == "__main__":
    client = TCPClient('127.0.0.1', 8888)
    client.connect()

服务器程序:

python 复制代码
import socket
import threading
import time

class TCPServer:
    def __init__(self, handle_client=None, host='127.0.0.1', port=8888):
        self.host = host
        self.port = port
        self.server_socket = None
        self.running = False
        self.handle_client = handle_client
        
    def start(self):
        """启动TCP服务器"""
        try:
            # 创建socket对象
            self.server_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
            
            # 设置地址重用
            self.server_socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
            
            # 绑定地址和端口
            self.server_socket.bind((self.host, self.port))
            
            # 开始监听,最多允许5个连接排队
            self.server_socket.listen(5)
            
            print(f"✅ TCP服务器启动在 {self.host}:{self.port}")
            print("等待客户端连接...")
            
            self.running = True
            
            while self.running:
                try:
                    # 接受客户端连接
                    client_socket, client_address = self.server_socket.accept()
                    print(f"🔗 新客户端连接: {client_address}")
                    
                    # 为每个客户端创建新线程处理
                    client_thread = threading.Thread(
                        target=self.handle_client,
                        args=(client_socket, client_address)
                    )
                    client_thread.daemon = True
                    client_thread.start()
                    
                except KeyboardInterrupt:
                    print("\n🛑 服务器正在关闭...")
                    break
                except Exception as e:
                    print(f"❌ 接受连接时出错: {e}")
                    continue
                    
        except Exception as e:
            print(f"❌ 服务器启动失败: {e}")
        finally:
            self.stop()

    def stop(self):
        """停止服务器"""
        self.running = False
        if self.server_socket:
            self.server_socket.close()
            print("🛑 TCP服务器已停止")


def handle_client(client_socket, client_address):
        """处理客户端连接"""
        try:
            # 发送欢迎消息
            # welcome_msg = f"欢迎连接到TCP服务器 {host}:{port}!\n输入 'exit' 断开连接\n"
            # client_socket.send(welcome_msg.encode('utf-8'))
            
            while True:
                # 接收客户端数据
                data = client_socket.recv(1024)
                if not data:
                    print(f"📭 客户端 {client_address} 断开连接")
                    break
                
                # 解码数据
                message = data.decode('utf-8').strip()
                print(f"📥 来自 {client_address} 的消息: {message}")
                
                # 处理退出命令
                if message.lower() == 'exit':
                    response = "再见!连接已关闭\n"
                    client_socket.send(response.encode('utf-8'))
                    break
                
                # 处理特殊命令
                if message.lower() == 'time':
                    response = f"当前时间: {time.strftime('%Y-%m-%d %H:%M:%S')}\n"
                elif message.lower() == 'help':
                    response = "可用命令:\n  time - 获取服务器时间\n  exit - 断开连接\n  help - 显示帮助\n"
                else:
                    # 回声服务:将消息原样返回
                    response = f"服务器回复: {message}\n"
                
                # 发送响应
                client_socket.send(response.encode('utf-8'))
        except ConnectionResetError:
            print(f"⚠️  客户端 {client_address} 强制断开连接")
        except Exception as e:
            print(f"❌ 处理客户端 {client_address} 时出错: {e}")
        finally:
            # 关闭客户端socket
            client_socket.close()

if __name__ == "__main__":
    server = TCPServer(handle_client,'127.0.0.1', 8888)
    server.start()
相关推荐
Trouvaille ~1 天前
【Linux】网络编程基础(三):Socket编程预备知识
linux·运维·服务器·网络·c++·socket·网络字节序
我在人间贩卖青春10 天前
Socket套接字与TCP实现框架
网络·网络协议·tcp/ip·socket
七夜zippoe11 天前
Python网络编程实战:从TCP/IP到WebSocket的协议演进与核心技术解析
网络·python·websocket·tcp/ip·socket·心跳机制
独断万古他化15 天前
【Java 网络编程全解】Socket 套接字与 TCP/UDP 通信实战全解
java·网络编程·socket
起个名字费劲死了20 天前
QT + Socket 客户端/服务端 公网通讯
服务器·c++·qt·socket
weixin79893765432...25 天前
深入浅出 WebSocket 协议
websocket·http·socket·sse
爬点儿啥1 个月前
[Ai Agent] 13 用 Streamlit 为 Agents SDK 打造可视化“驾驶舱”
人工智能·ai·状态模式·agent·streamlit·智能体
2401_841495641 个月前
【游戏开发】坦克大战
python·游戏·socket·pygame·tkinter·pyinstaller·坦克大战
better_liang1 个月前
每日Java面试场景题知识点之-TCP/IP协议栈与Socket编程
java·tcp/ip·计算机网络·网络编程·socket·面试题