作者 :丁林松 cnsilan@163.com
Qt嵌入式开发技术
目录
- Qt常用部件与基础类
- 线程同步与进程管理
- OA文字处理器实例
- 绘画与事件处理
- 多窗口应用开发
- 屏幕截图程序
- 数据库编程
- 网络编程实现
- Web浏览器开发
- 局域网聊天系统
- 移动平台开发
- QML编程实例
- 软件安全与逆向
- CAN FD总线医疗应用
- 完整代码示例
在嵌入式Qt开发中,合理选择和优化UI部件对系统性能和用户体验至关重要。本节将深入探讨Qt核心部件在资源受限环境下的最佳实践。
核心Widget类层次结构
在嵌入式Qt开发中,QWidget作为所有用户界面对象的基类,其内存管理和渲染性能直接影响系统响应性。QObject提供信号槽机制,是Qt事件驱动架构的核心。对于嵌入式设备,我们需要重点关注轻量级部件的使用。
嵌入式优化的Widget选择策略:
- QLabel:用于静态文本显示,内存占用小,适合状态指示
- QPushButton:按钮控件,支持触摸优化,可自定义样式减少资源消耗
- QLineEdit:单行文本输入,对于触屏设备需要配置虚拟键盘
- QProgressBar:进度显示,在数据传输和处理过程中提供视觉反馈
- QTableWidget:表格显示,需要注意大数据量时的性能优化
I/O类在嵌入式设备中的应用
Qt的I/O框架为嵌入式设备提供了强大的数据处理能力。QIODevice作为所有I/O设备的抽象基类,支持同步和异步操作模式。在嵌入式系统中,我们经常需要处理串口通信、文件操作和网络传输。
关键I/O类应用场景:
- QSerialPort:串口通信,连接传感器、执行器等外围设备
- QFile:文件操作,配置文件读写、日志记录
- QTimer:定时器,周期性任务执行、看门狗实现
- QThread:多线程处理,避免UI阻塞
图形界面美工与嵌入式UI设计
嵌入式设备的UI设计需要平衡美观性和性能。Qt提供了强大的样式表系统(QSS),允许开发者创建专业的界面外观。在资源受限的环境中,合理使用QSS可以减少图片资源的使用,降低内存占用。
嵌入式UI优化原则:
- 使用矢量图标替代位图,减少不同分辨率下的适配问题
- 采用CSS渐变效果替代复杂背景图片
- 合理使用缓存机制,避免重复绘制
- 响应式布局设计,适应不同屏幕尺寸
- 触摸友好的按钮尺寸设计(最小44px×44px)
Qt线程同步与进程管理在嵌入式系统中的实现
嵌入式系统通常需要处理多个并发任务,如数据采集、通信处理、用户界面更新等。Qt的线程模型为这些需求提供了优雅的解决方案。QThread、QMutex、QSemaphore等类构成了Qt的多线程编程框架。
嵌入式多线程架构设计
在嵌入式Qt应用中,典型的线程架构包括:
- 主线程(GUI线程):负责用户界面更新和事件处理
- 数据采集线程:处理传感器数据读取和预处理
- 通信线程:处理网络通信或串口通信
- 存储线程:负责数据库操作和文件I/O
- 算法处理线程:执行复杂的数据分析和处理任务
线程间通信机制
Qt提供了多种线程间通信方式,在嵌入式系统中需要根据实际需求选择合适的机制:
信号槽机制
适用于松耦合的异步通信,Qt会自动处理跨线程调用的同步问题。在嵌入式系统中特别适合状态更新和事件通知。
共享内存
使用QSharedMemory在进程间共享大量数据,减少内存拷贝开销,适合高性能数据处理场景。
实时性能优化策略
嵌入式系统对实时性要求较高,以下是Qt多线程编程中的性能优化策略:
- 合理设置线程优先级,确保关键任务的实时响应
- 使用无锁编程技术,减少线程竞争
- 采用生产者-消费者模式处理数据流
- 避免在关键路径上使用重量级同步原语
- 利用Qt的事件循环机制实现高效的异步处理
基于Qt的嵌入式OA文字处理器开发实例
在嵌入式工业控制系统或医疗设备中,文字处理功能是常见需求。本节将详细介绍如何使用Qt开发一个轻量级的文字处理器,适用于触屏操作和资源受限环境。
核心架构设计
嵌入式文字处理器的核心组件包括:
- QTextEdit:富文本编辑器,支持格式化文本显示和编辑
- QTextDocument:文档模型,管理文本内容和格式
- QTextCursor:光标控制,实现文本选择和编辑操作
- QTextCharFormat:字符格式控制,设置字体、颜色等属性
- QPrintDialog:打印功能,支持网络打印机连接
触屏优化与手势支持
针对嵌入式触屏设备,文字处理器需要特殊的交互设计:
触屏交互优化措施:
- 放大文本选择手柄,便于精确操作
- 实现双击放大功能,提高文本可读性
- 添加手势识别,支持滑动滚动和缩放
- 优化虚拟键盘集成,减少输入延迟
- 实现上下文菜单的触摸友好版本
文档格式与存储优化
在嵌入式系统中,文档的存储和加载速度至关重要。采用轻量级的文档格式可以显著提升性能:
格式支持策略
- 原生Qt格式(最快加载)
- 纯文本格式(最小存储)
- HTML格式(兼容性好)
- RTF格式(格式保持)
性能优化技术
- 增量保存机制
- 后台自动保存
- 文档分块加载
- 图片压缩存储
绘画与鼠标事件处理及坐标系转换
在嵌入式Qt应用中,自定义绘图和事件处理是实现专业用户界面的关键技术。QPainter类提供了强大的2D绘图能力,结合事件系统可以创建交互式的图形界面。
QPainter绘图系统深度应用
在嵌入式设备中,高效的绘图渲染对用户体验至关重要:
- 渲染优化:使用QPainter::Antialiasing获得平滑线条,但需权衡性能
- 缓存策略:对复杂图形使用QPixmap缓存,避免重复绘制
- 坐标系统:合理使用translate()、scale()、rotate()变换
- 绘图路径:使用QPainterPath优化复杂形状绘制
事件处理机制与用户交互
Qt的事件系统是构建响应式用户界面的基础。在嵌入式系统中,需要特别关注触摸事件和多点触控支持:
关键事件类型处理:
- mousePressEvent:处理点击开始,记录初始位置
- mouseMoveEvent:实现拖拽功能,更新图形位置
- mouseReleaseEvent:完成交互操作,触发相应动作
- wheelEvent:处理滚轮缩放,适配触摸板手势
- touchEvent:多点触控支持,手势识别
坐标系转换与数学计算
在绘图应用中,坐标系转换是常见需求。Qt提供了完整的2D变换矩阵支持,可以实现复杂的几何变换:
常用坐标转换技术:
世界坐标到屏幕坐标
用于将逻辑坐标映射到显示设备
视口变换
实现缩放和平移功能
旋转变换
支持任意角度的图形旋转
投影变换
3D效果的2D投影显示
窗口关闭事件与资源管理
在嵌入式系统中,正确处理应用程序的关闭事件对系统稳定性至关重要:
关闭事件处理策略:
- 重写closeEvent()方法,实现自定义关闭逻辑
- 保存用户数据和应用状态
- 释放硬件资源(串口、网络连接等)
- 停止后台线程和定时器
- 实现优雅退出机制,避免数据丢失
多窗口嵌入式应用架构设计与实现
在复杂的嵌入式系统中,多窗口架构能够提供更好的用户体验和功能组织。Qt的窗口管理系统支持模态对话框、非模态窗口、MDI(多文档界面)等多种窗口模式。
窗口管理策略
嵌入式多窗口应用的设计原则:
- 主窗口(MainWindow):应用程序的核心界面,管理全局状态
- 功能窗口:独立的功能模块,如设置面板、数据显示窗口
- 对话框:临时交互窗口,用于用户输入和确认操作
- 工具窗口:辅助功能窗口,如调试面板、状态监控
窗口间通信机制
多窗口应用中,窗口间的数据共享和事件传递是关键技术点:
信号槽连接
最常用的窗口间通信方式
- 类型安全的连接方式
- 支持一对多广播
- 自动生命周期管理
事件传递
底层事件机制通信
- 自定义事件类型
- 事件过滤器应用
- 跨线程事件处理
内存管理与性能优化
在资源受限的嵌入式环境中,多窗口应用的内存管理需要特别关注:
优化策略:
- 延迟创建:按需创建窗口实例,减少启动时间
- 窗口缓存:频繁使用的窗口保持在内存中
- 资源共享:多窗口共享图标、字体等资源
- 智能销毁:不常用窗口的自动回收机制
响应式布局设计
嵌入式设备的屏幕尺寸多样,多窗口应用需要适应不同的显示环境:
适配技术要点:
📱
小屏设备
标签页切换模式
💻
中等屏幕
停靠窗口布局
🖥️
大屏显示
多窗口并列显示
嵌入式屏幕截图与图像处理系统
在嵌入式系统中,屏幕截图功能常用于远程监控、故障诊断和用户操作记录。Qt提供了强大的屏幕捕获API,结合图像处理功能可以实现专业的截图工具。
屏幕捕获技术实现
Qt屏幕截图的核心API:
- QScreen::grabWindow():捕获指定窗口内容
- QGuiApplication::primaryScreen():获取主显示屏
- QPixmap::grabWidget():捕获特定Widget内容
- QQuickWindow::grabWindow():捕获QML界面
高级截图功能开发
专业截图工具需要提供丰富的用户交互功能:
功能特性实现:
- 区域选择:鼠标拖拽选择截图区域
- 形状工具:矩形、椭圆、箭头标注
- 文字标注:在截图上添加文字说明
- 模糊效果:敏感信息的模糊处理
- 延时截图:定时自动截图功能
图像处理与优化
在嵌入式环境中,图像处理需要平衡质量和性能:
压缩算法选择
- PNG:无损压缩,适合图表截图
- JPEG:有损压缩,适合照片类截图
- WebP:现代格式,平衡质量和大小
- BMP:无压缩,适合快速处理
性能优化技巧
- 异步图像处理
- 内存池管理
- 硬件加速利用
- 批量处理模式
远程截图与网络传输
嵌入式设备的截图功能常需要支持远程访问:
网络传输优化:
- 实时流式传输(类似VNC协议)
- 差异帧传输,减少带宽占用
- 自适应质量调整
- 断线重连机制
- 安全加密传输
嵌入式数据库编程与数据管理
嵌入式系统中的数据管理对系统性能和可靠性至关重要。Qt SQL模块提供了统一的数据库访问接口,支持SQLite、MySQL、PostgreSQL等多种数据库系统。在资源受限的嵌入式环境中,SQLite是最常用的选择。
嵌入式数据库架构设计
Qt SQL框架的核心组件:
- QSqlDatabase:数据库连接管理,支持连接池
- QSqlQuery:SQL语句执行,支持预处理语句
- QSqlTableModel:表格模型,直接与QTableView绑定
- QSqlRecord:记录对象,便于数据访问
- QSqlError:错误处理,提供详细的错误信息
SQLite优化策略
在嵌入式系统中,SQLite的性能优化对用户体验影响显著:
关键优化技术:
- 索引设计:为常用查询字段创建合适的索引
- 事务管理:批量操作使用事务,提高写入性能
- 连接复用:避免频繁的数据库连接创建和销毁
- WAL模式:启用Write-Ahead Logging,提高并发性能
- PRAGMA优化:调整SQLite运行参数
数据模型与视图架构
Qt的模型/视图架构为数据库应用提供了强大的支持:
QSqlTableModel
单表操作模型,支持自动刷新和缓存
QSqlQueryModel
查询结果模型,适合复杂查询和只读显示
QSqlRelationalTableModel
关系表模型,支持外键关联显示
数据安全与备份策略
嵌入式系统的数据安全需要特别关注:
安全措施:
- 数据加密:敏感数据的加密存储
- 访问控制:基于角色的数据访问权限
- 自动备份:定期数据备份到外部存储
- 完整性检查:数据一致性验证机制
- 审计日志:记录所有数据修改操作
基于套接字的嵌入式网络编程
网络通信是现代嵌入式系统的核心功能之一。Qt Network模块提供了从低级套接字到高级HTTP客户端的完整网络编程支持。在嵌入式环境中,网络编程需要考虑连接稳定性、功耗管理和实时性要求。
套接字编程基础架构
Qt网络编程的核心类:
- QTcpSocket:TCP客户端实现,可靠的数据传输
- QTcpServer:TCP服务器实现,支持多客户端连接
- QUdpSocket:UDP通信实现,适合实时数据传输
- QNetworkAccessManager:高级HTTP客户端
- QSslSocket:SSL/TLS加密通信
协议设计与数据序列化
嵌入式系统的网络协议设计需要考虑带宽限制和处理能力:
协议设计原则:
- 二进制协议:使用QDataStream实现高效的数据序列化
- 消息分片:大数据的分包传输机制
- 心跳检测:连接状态监控和自动重连
- 压缩传输:使用QCompress减少网络负载
- 错误重传:关键数据的可靠传输保证
异步网络编程模式
Qt的事件驱动架构天然支持异步网络编程:
信号槽异步处理
- readyRead()信号处理数据接收
- connected()信号处理连接建立
- error()信号处理网络错误
- 非阻塞I/O操作
多线程网络处理
- 独立的网络处理线程
- 线程间数据队列
- 连接池管理
- 负载均衡策略
嵌入式网络优化技术
针对嵌入式设备的网络优化策略:
性能优化措施:
连接管理
- Keep-Alive连接复用
- 连接超时控制
- 自适应重连间隔
数据优化
- 数据缓存机制
- 增量更新传输
- 优先级队列处理
嵌入式Web浏览器开发与集成
在现代嵌入式系统中,Web浏览器功能越来越重要,特别是在工业人机界面和智能设备中。Qt WebEngine模块基于Chromium内核,提供了完整的现代Web浏览器功能。
WebEngine架构与集成
Qt WebEngine的核心组件:
- QWebEngineView:Web页面显示组件
- QWebEnginePage:页面逻辑处理,JavaScript交互
- QWebEngineProfile:浏览器配置文件,缓存和Cookie管理
- QWebEngineSettings:浏览器设置,安全策略配置
- QWebChannel:Qt与JavaScript双向通信
嵌入式浏览器优化
在资源受限的嵌入式环境中,浏览器性能优化至关重要:
性能优化策略:
- 内存限制:设置合理的内存使用上限
- 缓存管理:控制磁盘缓存大小和策略
- 功能裁剪:禁用不需要的Web功能
- 硬件加速:启用GPU加速渲染
- 预加载优化:关键页面的预加载机制
Qt与Web的双向集成
QWebChannel提供了Qt应用与Web页面的无缝集成:
Qt到Web
- 设备状态实时推送
- 传感器数据更新
- 系统事件通知
- 配置参数同步
Web到Qt
- 用户操作响应
- 设备控制命令
- 文件上传下载
- 数据查询请求
安全与隐私保护
嵌入式浏览器的安全配置需要特别关注:
安全配置要点:
- 同源策略:严格的跨域访问控制
- 内容过滤:阻止恶意脚本和插件
- HTTPS强制:安全连接策略
- Cookie管理:隐私保护和会话安全
- 下载控制:文件下载权限管理
局域网聊天系统的嵌入式实现
局域网聊天系统在工业控制、医疗设备和团队协作中具有重要应用价值。基于Qt开发的聊天系统可以实现设备间的实时通信、状态共享和协同操作。
分布式架构设计
嵌入式聊天系统的架构选择:
- P2P模式:无中心服务器,设备直接通信
- 中心服务器模式:统一的消息路由和存储
- 混合模式:结合P2P和服务器的优势
- 广播发现:自动发现局域网内的设备
消息协议与数据格式
高效的消息协议设计对系统性能至关重要:
消息类型设计:
- 文本消息:UTF-8编码的文本内容
- 文件传输:分块传输机制,支持断点续传
- 状态同步:设备状态和配置信息共享
- 系统命令:远程控制和操作指令
- 心跳包:连接状态维护和检测
用户界面与交互设计
嵌入式聊天系统的UI设计需要考虑触屏操作和有限的屏幕空间:
界面组件
- QListWidget:好友和设备列表
- QTextBrowser:消息显示区域
- QLineEdit:消息输入框
- QProgressBar:文件传输进度
交互优化
- 虚拟键盘适配
- 消息气泡显示
- 滑动手势支持
- 语音消息集成
安全与权限管理
工业和医疗环境对通信安全要求严格:
安全措施:
- 身份认证:基于证书或密钥的设备认证
- 消息加密:AES加密保护通信内容
- 权限控制:分级权限管理系统
- 审计日志:完整的通信记录
- 网络隔离:VLAN和防火墙配置
Qt移动平台开发与嵌入式移植
Qt for Mobile提供了跨平台的移动应用开发能力,支持Android和iOS平台。在嵌入式移动设备开发中,Qt能够提供原生性能和丰富的功能支持。
移动平台架构适配
Qt移动平台的核心技术:
- Qt Quick:现代化的移动UI框架
- Qt Sensors:传感器数据访问
- Qt Positioning:GPS和定位服务
- Qt Multimedia:音视频处理能力
- Qt Connectivity:蓝牙和NFC通信
硬件接口与传感器集成
移动嵌入式设备通常集成了丰富的传感器和硬件接口:
传感器集成应用:
- 加速度计:设备姿态检测和运动感知
- 陀螺仪:精确的角度变化测量
- 磁力计:指南针功能和方向定位
- 环境光传感器:自动亮度调节
- 温湿度传感器:环境监测功能
性能优化与功耗管理
移动嵌入式设备的性能和功耗平衡是关键挑战:
性能优化
- QML编译器优化
- 图像缓存策略
- 动画性能调优
- 内存使用监控
功耗管理
- CPU频率调节
- 后台任务管理
- 屏幕亮度控制
- 网络连接优化
跨平台部署策略
Qt的跨平台特性简化了多平台部署:
部署优化技术:
📱
Android
APK打包优化
🍎
iOS
App Store配置
🐧
嵌入式Linux
自定义发行版
QML现代化嵌入式界面编程
QML(Qt Meta-Object Language)是Qt的声明式UI语言,特别适合创建现代化的嵌入式用户界面。QML结合JavaScript逻辑,提供了流畅的动画效果和响应式设计能力。
QML架构与性能优化
QML的核心组件架构:
- QtQuick:基础UI元素和布局系统
- QtQuick.Controls:标准控件库
- QtQuick.Layouts:高级布局管理
- Qt.labs.platform:原生平台集成
- 自定义组件:可复用的业务组件
数据绑定与状态管理
QML的数据绑定机制是其强大功能的核心:
数据绑定技术:
- 属性绑定:自动更新的响应式数据流
- 信号处理:事件驱动的交互逻辑
- 状态机:复杂UI状态的管理
- 模型视图:大量数据的高效显示
- C++集成:与后端逻辑的无缝连接
动画与视觉效果
QML提供了丰富的动画和视觉效果支持:
基础动画
- PropertyAnimation
- NumberAnimation
- ColorAnimation
- RotationAnimation
高级效果
- ParticleSystem
- ShaderEffect
- PathView
- Canvas绘图
性能优化
- GPU加速渲染
- 批量更新机制
- 异步图像加载
- 内存池管理
嵌入式QML最佳实践
在嵌入式环境中使用QML的优化建议:
性能优化指南:
- 使用预编译QML,减少启动时间
- 合理使用Loader组件,按需加载界面
- 避免复杂的JavaScript计算
- 优化图片资源,使用合适的格式和尺寸
- 利用QML Profiler进行性能分析
软件设计安全与逆向工程防护
嵌入式Qt应用的安全性对于工业控制、医疗设备和关键基础设施至关重要。本节将深入探讨安全威胁分析、防护机制设计和安全编程实践。
安全威胁模型分析
嵌入式Qt应用面临的主要安全威胁:
- 逆向工程:代码反编译和算法提取
- 缓冲区溢出:内存安全漏洞利用
- 代码注入:恶意代码植入攻击
- 数据泄露:敏感信息的非授权访问
- 通信拦截:网络传输数据的窃取
逆向工程防护技术
针对Qt应用的反逆向技术实现:
代码保护策略:
- 代码混淆:函数名和变量名的随机化
- 控制流平坦化:破坏程序的逻辑结构
- 反调试技术:检测和阻止调试器附加
- 代码加密:运行时解密关键代码段
- 完整性校验:检测文件篡改
缓冲区溢出防护
Qt应用中的内存安全编程实践:
编译器保护
- 栈保护机制(Stack Canary)
- 地址空间随机化(ASLR)
- 数据执行防护(DEP/NX)
- 控制流完整性(CFI)
编程实践
- 使用Qt容器类避免原始指针
- 智能指针的正确使用
- 输入验证和边界检查
- 异常安全的代码设计
内存检测与调试工具
Qt开发中的内存安全检测工具链:
检测工具应用:
Valgrind
内存泄露和访问错误检测
AddressSanitizer
快速的内存错误检测
Qt Creator分析器
集成的性能和内存分析
安全编程规范
建立安全的Qt开发流程和规范:
安全开发生命周期:
- 需求分析阶段:安全需求的识别和评估
- 设计阶段:威胁建模和安全架构设计
- 编码阶段:安全编程规范的执行
- 测试阶段:安全测试和漏洞扫描
- 部署阶段:安全配置和监控机制
Qt与CAN FD总线在移动医疗设备中的实践应用
CAN FD(Controller Area Network with Flexible Data-Rate)是下一代车载和工业通信协议,在移动医疗设备中具有重要应用价值。Qt提供了完整的CAN总线支持,能够实现可靠的医疗设备互联和数据传输。
CAN FD协议特性与优势
CAN FD相比传统CAN的改进:
- 更高带宽:数据阶段最高可达8Mbps传输速率
- 更大载荷:单帧最大支持64字节数据传输
- 更好错误检测:增强的CRC校验机制
- 向下兼容:与传统CAN网络共存
- 实时性保证:确定性的消息传输延迟
Qt CAN总线编程接口
Qt SerialBus模块提供了统一的CAN总线访问接口:
核心API组件:
- QCanBusDevice:CAN设备抽象接口
- QCanBusFrame:CAN帧数据结构
- QCanBusDeviceInfo:设备信息查询
- 插件系统:支持多种CAN硬件适配器
- 异步I/O:非阻塞的帧收发处理
医疗设备通信协议设计
医疗设备间的CAN FD通信协议需要考虑安全性和可靠性:
协议层次结构
- 物理层:CAN FD收发器
- 数据链路层:帧格式和仲裁
- 网络层:设备寻址和路由
- 应用层:医疗数据协议
消息类型定义
- 生命体征数据传输
- 设备状态监控
- 报警信息广播
- 配置参数同步
实时数据处理与可视化
医疗数据的实时处理和可视化是Qt应用的核心功能:
数据处理流程:
- 数据采集:高频率的传感器数据读取
- 信号处理:数字滤波和噪声消除
- 数据融合:多传感器信息的综合处理
- 实时显示:波形图表和数值显示
- 存储归档:历史数据的压缩存储
设备驱动控制实现
基于Qt的医疗设备驱动控制系统设计:
控制系统架构:
🏥
监护设备
心电、血压、血氧
💊
输液设备
智能输液泵控制
🔧
维护系统
预防性维护管理
安全与法规合规
医疗设备软件必须符合严格的安全标准和法规要求:
合规要求:
- IEC 62304:医疗设备软件生命周期标准
- ISO 14155:医疗器械临床试验规范
- HIPAA:医疗数据隐私保护法规
- FDA 510(k):美国医疗器械上市许可
- CE标识:欧盟医疗器械认证
完整PySide6代码示例
以下是本文档中讨论的各种技术的完整PySide6实现示例,展示了嵌入式Qt开发的实际应用。
# -*- coding: utf-8 -*-
"""
Qt嵌入式开发技术综合示例
包含多窗口管理、网络通信、数据库操作、CAN总线通信等功能
适用于工业控制和医疗设备应用
"""
import sys
import json
import sqlite3
import threading
import time
from datetime import datetime
from typing import Optional, Dict, Any, List
import hashlib
import logging
from PySide6.QtWidgets import (
QApplication, QMainWindow, QVBoxLayout, QHBoxLayout, QGridLayout,
QWidget, QLabel, QPushButton, QLineEdit, QTextEdit, QTableWidget,
QTableWidgetItem, QProgressBar, QTabWidget, QSplitter, QGroupBox,
QMessageBox, QFileDialog, QSystemTrayIcon, QMenu, QToolBar,
QStatusBar, QDialog, QDialogButtonBox, QListWidget, QSpinBox,
QSlider, QCheckBox, QComboBox, QFrame, QScrollArea
)
from PySide6.QtCore import (
QThread, QTimer, QObject, Signal, Slot, QMutex, QWaitCondition,
QTcpServer, QTcpSocket, QUdpSocket, QIODevice, QByteArray,
QSettings, QStandardPaths, QDir, QFileInfo, QDateTime,
QPropertyAnimation, QEasingCurve, QRect, QPoint, QSize,
QAbstractTableModel, Qt, QModelIndex, QVariant
)
from PySide6.QtGui import (
QPainter, QPen, QBrush, QColor, QFont, QPixmap, QPalette,
QIcon, QAction, QShortcut, QKeySequence, QValidator,
QIntValidator, QDoubleValidator, QRegularExpressionValidator
)
from PySide6.QtNetwork import (
QNetworkAccessManager, QNetworkRequest, QNetworkReply,
QHostAddress, QAbstractSocket
)
from PySide6.QtSql import QSqlDatabase, QSqlQuery, QSqlTableModel, QSqlError
# 日志配置
logging.basicConfig(
level=logging.INFO,
format='%(asctime)s - %(levelname)s - %(message)s',
handlers=[
logging.FileHandler('embedded_app.log'),
logging.StreamHandler()
]
)
logger = logging.getLogger(__name__)
class DatabaseManager:
"""数据库管理类 - 处理SQLite数据库操作"""
def __init__(self, db_path: str = "embedded_data.db"):
self.db_path = db_path
self.connection = None
self.init_database()
def init_database(self):
"""初始化数据库连接和表结构"""
try:
self.connection = QSqlDatabase.addDatabase("QSQLITE")
self.connection.setDatabaseName(self.db_path)
if not self.connection.open():
logger.error(f"无法打开数据库: {self.connection.lastError().text()}")
return
# 创建设备数据表
query = QSqlQuery()
create_table_sql = """
CREATE TABLE IF NOT EXISTS device_data (
id INTEGER PRIMARY KEY AUTOINCREMENT,
device_id TEXT NOT NULL,
timestamp DATETIME DEFAULT CURRENT_TIMESTAMP,
sensor_type TEXT NOT NULL,
value REAL NOT NULL,
unit TEXT,
status TEXT DEFAULT 'normal'
)
"""
if not query.exec(create_table_sql):
logger.error(f"创建表失败: {query.lastError().text()}")
# 创建用户会话表
session_table_sql = """
CREATE TABLE IF NOT EXISTS user_sessions (
id INTEGER PRIMARY KEY AUTOINCREMENT,
username TEXT NOT NULL,
login_time DATETIME DEFAULT CURRENT_TIMESTAMP,
logout_time DATETIME,
ip_address TEXT,
session_token TEXT UNIQUE
)
"""
if not query.exec(session_table_sql):
logger.error(f"创建会话表失败: {query.lastError().text()}")
logger.info("数据库初始化成功")
except Exception as e:
logger.error(f"数据库初始化异常: {e}")
def insert_sensor_data(self, device_id: str, sensor_type: str,
value: float, unit: str = "") -> bool:
"""插入传感器数据"""
try:
query = QSqlQuery()
query.prepare("""
INSERT INTO device_data (device_id, sensor_type, value, unit)
VALUES (?, ?, ?, ?)
""")
query.addBindValue(device_id)
query.addBindValue(sensor_type)
query.addBindValue(value)
query.addBindValue(unit)
if query.exec():
return True
else:
logger.error(f"插入数据失败: {query.lastError().text()}")
return False
except Exception as e:
logger.error(f"插入数据异常: {e}")
return False
def get_latest_data(self, device_id: str, limit: int = 100) -> List[Dict]:
"""获取最新的设备数据"""
try:
query = QSqlQuery()
query.prepare("""
SELECT * FROM device_data
WHERE device_id = ?
ORDER BY timestamp DESC
LIMIT ?
""")
query.addBindValue(device_id)
query.addBindValue(limit)
data = []
if query.exec():
while query.next():
record = {
'id': query.value(0),
'device_id': query.value(1),
'timestamp': query.value(2),
'sensor_type': query.value(3),
'value': query.value(4),
'unit': query.value(5),
'status': query.value(6)
}
data.append(record)
return data
except Exception as e:
logger.error(f"查询数据异常: {e}")
return []
class NetworkManager(QObject):
"""网络管理类 - 处理TCP/UDP通信"""
data_received = Signal(str, bytes)
connection_status_changed = Signal(bool)
def __init__(self):
super().__init__()
self.tcp_server = QTcpServer()
self.tcp_clients = []
self.udp_socket = QUdpSocket()
self.network_manager = QNetworkAccessManager()
# 设置TCP服务器
self.tcp_server.newConnection.connect(self.handle_new_connection)
# 设置UDP套接字
self.udp_socket.readyRead.connect(self.handle_udp_data)
def start_tcp_server(self, port: int = 8888) -> bool:
"""启动TCP服务器"""
try:
if self.tcp_server.listen(QHostAddress.Any, port):
logger.info(f"TCP服务器启动成功,端口: {port}")
self.connection_status_changed.emit(True)
return True
else:
logger.error(f"TCP服务器启动失败: {self.tcp_server.errorString()}")
return False
except Exception as e:
logger.error(f"启动TCP服务器异常: {e}")
return False
@Slot()
def handle_new_connection(self):
"""处理新的TCP连接"""
client = self.tcp_server.nextPendingConnection()
self.tcp_clients.append(client)
client.readyRead.connect(lambda: self.handle_tcp_data(client))
client.disconnected.connect(lambda: self.handle_client_disconnect(client))
logger.info(f"新客户端连接: {client.peerAddress().toString()}")
def handle_tcp_data(self, client: QTcpSocket):
"""处理TCP数据"""
try:
data = client.readAll()
peer_address = client.peerAddress().toString()
self.data_received.emit(peer_address, data.data())
logger.info(f"收到TCP数据来自 {peer_address}: {len(data)} 字节")
except Exception as e:
logger.error(f"处理TCP数据异常: {e}")
def handle_client_disconnect(self, client: QTcpSocket):
"""处理客户端断开连接"""
if client in self.tcp_clients:
self.tcp_clients.remove(client)
logger.info(f"客户端断开连接: {client.peerAddress().toString()}")
def bind_udp_socket(self, port: int = 9999) -> bool:
"""绑定UDP套接字"""
try:
if self.udp_socket.bind(QHostAddress.Any, port):
logger.info(f"UDP套接字绑定成功,端口: {port}")
return True
else:
logger.error(f"UDP套接字绑定失败")
return False
except Exception as e:
logger.error(f"绑定UDP套接字异常: {e}")
return False
@Slot()
def handle_udp_data(self):
"""处理UDP数据"""
try:
while self.udp_socket.hasPendingDatagrams():
datagram, sender, port = self.udp_socket.readDatagram(1024)
sender_address = sender.toString()
self.data_received.emit(sender_address, datagram)
logger.info(f"收到UDP数据来自 {sender_address}:{port}: {len(datagram)} 字节")
except Exception as e:
logger.error(f"处理UDP数据异常: {e}")
def broadcast_message(self, message: str, port: int = 9999):
"""广播消息"""
try:
data = message.encode('utf-8')
bytes_sent = self.udp_socket.writeDatagram(
data, QHostAddress.Broadcast, port
)
logger.info(f"广播消息: {message} ({bytes_sent} 字节)")
except Exception as e:
logger.error(f"广播消息异常: {e}")
class CANBusSimulator(QObject):
"""CAN总线模拟器 - 模拟医疗设备通信"""
frame_received = Signal(dict)
def __init__(self):
super().__init__()
self.running = False
self.timer = QTimer()
self.timer.timeout.connect(self.simulate_medical_data)
self.device_configs = {
'ECG_MONITOR': {'id': 0x101, 'interval': 100}, # 心电监护仪
'BP_MONITOR': {'id': 0x102, 'interval': 5000}, # 血压监护仪
'SPO2_SENSOR': {'id': 0x103, 'interval': 1000}, # 血氧传感器
'INFUSION_PUMP': {'id': 0x201, 'interval': 2000} # 输液泵
}
def start_simulation(self):
"""启动CAN总线模拟"""
self.running = True
self.timer.start(100) # 100ms间隔
logger.info("CAN总线模拟启动")
def stop_simulation(self):
"""停止CAN总线模拟"""
self.running = False
self.timer.stop()
logger.info("CAN总线模拟停止")
@Slot()
def simulate_medical_data(self):
"""模拟医疗设备数据"""
if not self.running:
return
import random
current_time = int(time.time() * 1000)
# 模拟心电数据
if current_time % 100 == 0: # 每100ms
ecg_value = 80 + random.randint(-20, 20) # 心率60-100
frame = {
'id': 0x101,
'timestamp': current_time,
'device': 'ECG_MONITOR',
'data': {
'heart_rate': ecg_value,
'rhythm': 'normal' if 60 <= ecg_value <= 100 else 'abnormal'
}
}
self.frame_received.emit(frame)
# 模拟血压数据
if current_time % 5000 == 0: # 每5秒
systolic = 120 + random.randint(-20, 30)
diastolic = 80 + random.randint(-15, 20)
frame = {
'id': 0x102,
'timestamp': current_time,
'device': 'BP_MONITOR',
'data': {
'systolic': systolic,
'diastolic': diastolic,
'status': 'normal' if 90 <= systolic <= 140 and 60 <= diastolic <= 90 else 'high'
}
}
self.frame_received.emit(frame)
# 模拟血氧数据
if current_time % 1000 == 0: # 每1秒
spo2_value = 98 + random.randint(-3, 2)
frame = {
'id': 0x103,
'timestamp': current_time,
'device': 'SPO2_SENSOR',
'data': {
'spo2': max(85, min(100, spo2_value)),
'pulse': 75 + random.randint(-10, 15)
}
}
self.frame_received.emit(frame)
class DataVisualizationWidget(QWidget):
"""数据可视化组件 - 实时图表显示"""
def __init__(self):
super().__init__()
self.setup_ui()
self.data_history = {'heart_rate': [], 'blood_pressure': [], 'spo2': []}
self.max_points = 100
def setup_ui(self):
"""设置用户界面"""
layout = QVBoxLayout(self)
# 实时数值显示
self.values_group = QGroupBox("实时数值")
values_layout = QGridLayout(self.values_group)
self.heart_rate_label = QLabel("心率: -- bpm")
self.heart_rate_label.setStyleSheet("font-size: 18px; font-weight: bold; color: #e74c3c;")
values_layout.addWidget(QLabel("心率监护:"), 0, 0)
values_layout.addWidget(self.heart_rate_label, 0, 1)
self.bp_label = QLabel("血压: --/-- mmHg")
self.bp_label.setStyleSheet("font-size: 18px; font-weight: bold; color: #3498db;")
values_layout.addWidget(QLabel("血压监护:"), 1, 0)
values_layout.addWidget(self.bp_label, 1, 1)
self.spo2_label = QLabel("血氧: --%")
self.spo2_label.setStyleSheet("font-size: 18px; font-weight: bold; color: #27ae60;")
values_layout.addWidget(QLabel("血氧饱和度:"), 2, 0)
values_layout.addWidget(self.spo2_label, 2, 1)
layout.addWidget(self.values_group)
# 波形显示区域
self.waveform_area = QWidget()
self.waveform_area.setMinimumHeight(300)
self.waveform_area.setStyleSheet("background-color: black; border: 1px solid gray;")
layout.addWidget(self.waveform_area)
# 状态指示灯
self.status_group = QGroupBox("设备状态")
status_layout = QHBoxLayout(self.status_group)
self.ecg_status = QLabel("ECG: 正常")
self.ecg_status.setStyleSheet("color: green; font-weight: bold;")
status_layout.addWidget(self.ecg_status)
self.bp_status = QLabel("BP: 正常")
self.bp_status.setStyleSheet("color: green; font-weight: bold;")
status_layout.addWidget(self.bp_status)
self.spo2_status = QLabel("SpO2: 正常")
self.spo2_status.setStyleSheet("color: green; font-weight: bold;")
status_layout.addWidget(self.spo2_status)
layout.addWidget(self.status_group)
def update_medical_data(self, frame_data: dict):
"""更新医疗数据显示"""
device = frame_data.get('device', '')
data = frame_data.get('data', {})
if device == 'ECG_MONITOR':
heart_rate = data.get('heart_rate', 0)
rhythm = data.get('rhythm', 'unknown')
self.heart_rate_label.setText(f"心率: {heart_rate} bpm")
# 更新状态
if rhythm == 'normal':
self.ecg_status.setText("ECG: 正常")
self.ecg_status.setStyleSheet("color: green; font-weight: bold;")
else:
self.ecg_status.setText("ECG: 异常")
self.ecg_status.setStyleSheet("color: red; font-weight: bold;")
# 记录历史数据
self.data_history['heart_rate'].append(heart_rate)
if len(self.data_history['heart_rate']) > self.max_points:
self.data_history['heart_rate'].pop(0)
elif device == 'BP_MONITOR':
systolic = data.get('systolic', 0)
diastolic = data.get('diastolic', 0)
status = data.get('status', 'unknown')
self.bp_label.setText(f"血压: {systolic}/{diastolic} mmHg")
# 更新状态
if status == 'normal':
self.bp_status.setText("BP: 正常")
self.bp_status.setStyleSheet("color: green; font-weight: bold;")
else:
self.bp_status.setText("BP: 高血压")
self.bp_status.setStyleSheet("color: orange; font-weight: bold;")
elif device == 'SPO2_SENSOR':
spo2 = data.get('spo2', 0)
pulse = data.get('pulse', 0)
self.spo2_label.setText(f"血氧: {spo2}%")
# 更新状态
if spo2 >= 95:
self.spo2_status.setText("SpO2: 正常")
self.spo2_status.setStyleSheet("color: green; font-weight: bold;")
else:
self.spo2_status.setText("SpO2: 偏低")
self.spo2_status.setStyleSheet("color: red; font-weight: bold;")
# 记录历史数据
self.data_history['spo2'].append(spo2)
if len(self.data_history['spo2']) > self.max_points:
self.data_history['spo2'].pop(0)
# 触发重绘
self.waveform_area.update()
def paintEvent(self, event):
"""绘制波形图"""
super().paintEvent(event)
painter = QPainter(self.waveform_area)
painter.setRenderHint(QPainter.Antialiasing)
# 设置背景
painter.fillRect(self.waveform_area.rect(), QColor(0, 0, 0))
# 绘制网格
painter.setPen(QPen(QColor(40, 40, 40), 1))
grid_spacing = 20
for x in range(0, self.waveform_area.width(), grid_spacing):
painter.drawLine(x, 0, x, self.waveform_area.height())
for y in range(0, self.waveform_area.height(), grid_spacing):
painter.drawLine(0, y, self.waveform_area.width(), y)
# 绘制心率波形
if len(self.data_history['heart_rate']) > 1:
painter.setPen(QPen(QColor(231, 76, 60), 2)) # 红色
points = []
width = self.waveform_area.width()
height = self.waveform_area.height() // 3
for i, value in enumerate(self.data_history['heart_rate']):
x = int((i / len(self.data_history['heart_rate'])) * width)
y = int(height - ((value - 40) / 80) * height) # 归一化到40-120范围
points.append(QPoint(x, y))
for i in range(len(points) - 1):
painter.drawLine(points[i], points[i + 1])
# 绘制血氧波形
if len(self.data_history['spo2']) > 1:
painter.setPen(QPen(QColor(39, 174, 96), 2)) # 绿色
points = []
width = self.waveform_area.width()
height = self.waveform_area.height() // 3
base_y = self.waveform_area.height() * 2 // 3
for i, value in enumerate(self.data_history['spo2']):
x = int((i / len(self.data_history['spo2'])) * width)
y = int(base_y - ((value - 85) / 15) * height) # 归一化到85-100范围
points.append(QPoint(x, y))
for i in range(len(points) - 1):
painter.drawLine(points[i], points[i + 1])
painter.end()
class SettingsDialog(QDialog):
"""设置对话框"""
def __init__(self, parent=None):
super().__init__(parent)
self.setWindowTitle("系统设置")
self.setModal(True)
self.resize(400, 300)
self.setup_ui()
self.load_settings()
def setup_ui(self):
"""设置用户界面"""
layout = QVBoxLayout(self)
# 网络设置
network_group = QGroupBox("网络设置")
network_layout = QGridLayout(network_group)
network_layout.addWidget(QLabel("TCP端口:"), 0, 0)
self.tcp_port_edit = QLineEdit("8888")
self.tcp_port_edit.setValidator(QIntValidator(1024, 65535))
network_layout.addWidget(self.tcp_port_edit, 0, 1)
network_layout.addWidget(QLabel("UDP端口:"), 1, 0)
self.udp_port_edit = QLineEdit("9999")
self.udp_port_edit.setValidator(QIntValidator(1024, 65535))
network_layout.addWidget(self.udp_port_edit, 1, 1)
layout.addWidget(network_group)
# 数据库设置
db_group = QGroupBox("数据库设置")
db_layout = QGridLayout(db_group)
db_layout.addWidget(QLabel("数据保留天数:"), 0, 0)
self.data_retention_spin = QSpinBox()
self.data_retention_spin.setRange(1, 365)
self.data_retention_spin.setValue(30)
db_layout.addWidget(self.data_retention_spin, 0, 1)
self.auto_backup_check = QCheckBox("启用自动备份")
db_layout.addWidget(self.auto_backup_check, 1, 0, 1, 2)
layout.addWidget(db_group)
# 界面设置
ui_group = QGroupBox("界面设置")
ui_layout = QGridLayout(ui_group)
ui_layout.addWidget(QLabel("刷新间隔(ms):"), 0, 0)
self.refresh_interval_spin = QSpinBox()
self.refresh_interval_spin.setRange(100, 10000)
self.refresh_interval_spin.setValue(1000)
ui_layout.addWidget(self.refresh_interval_spin, 0, 1)
self.show_animations_check = QCheckBox("启用动画效果")
self.show_animations_check.setChecked(True)
ui_layout.addWidget(self.show_animations_check, 1, 0, 1, 2)
layout.addWidget(ui_group)
# 按钮组
button_box = QDialogButtonBox(
QDialogButtonBox.Ok | QDialogButtonBox.Cancel
)
button_box.accepted.connect(self.accept)
button_box.rejected.connect(self.reject)
layout.addWidget(button_box)
def load_settings(self):
"""加载设置"""
settings = QSettings("EmbeddedApp", "Medical")
self.tcp_port_edit.setText(settings.value("network/tcp_port", "8888"))
self.udp_port_edit.setText(settings.value("network/udp_port", "9999"))
self.data_retention_spin.setValue(int(settings.value("database/retention_days", 30)))
self.auto_backup_check.setChecked(settings.value("database/auto_backup", True, type=bool))
self.refresh_interval_spin.setValue(int(settings.value("ui/refresh_interval", 1000)))
self.show_animations_check.setChecked(settings.value("ui/show_animations", True, type=bool))
def save_settings(self):
"""保存设置"""
settings = QSettings("EmbeddedApp", "Medical")
settings.setValue("network/tcp_port", self.tcp_port_edit.text())
settings.setValue("network/udp_port", self.udp_port_edit.text())
settings.setValue("database/retention_days", self.data_retention_spin.value())
settings.setValue("database/auto_backup", self.auto_backup_check.isChecked())
settings.setValue("ui/refresh_interval", self.refresh_interval_spin.value())
settings.setValue("ui/show_animations", self.show_animations_check.isChecked())
def accept(self):
"""确定按钮处理"""
self.save_settings()
super().accept()
class MainWindow(QMainWindow):
"""主窗口类"""
def __init__(self):
super().__init__()
self.setWindowTitle("嵌入式医疗设备控制系统")
self.setGeometry(100, 100, 1200, 800)
# 初始化组件
self.database = DatabaseManager()
self.network_manager = NetworkManager()
self.can_simulator = CANBusSimulator()
# 设置UI
self.setup_ui()
self.setup_menu_bar()
self.setup_tool_bar()
self.setup_status_bar()
self.setup_connections()
# 启动服务
self.start_services()
logger.info("主窗口初始化完成")
def setup_ui(self):
"""设置用户界面"""
central_widget = QWidget()
self.setCentralWidget(central_widget)
# 主布局
main_layout = QHBoxLayout(central_widget)
# 左侧面板
left_panel = QWidget()
left_panel.setMaximumWidth(300)
left_layout = QVBoxLayout(left_panel)
# 设备连接状态
connection_group = QGroupBox("连接状态")
connection_layout = QVBoxLayout(connection_group)
self.tcp_status_label = QLabel("TCP服务器: 未启动")
self.udp_status_label = QLabel("UDP套接字: 未绑定")
self.can_status_label = QLabel("CAN总线: 未连接")
connection_layout.addWidget(self.tcp_status_label)
connection_layout.addWidget(self.udp_status_label)
connection_layout.addWidget(self.can_status_label)
left_layout.addWidget(connection_group)
# 设备列表
device_group = QGroupBox("医疗设备")
device_layout = QVBoxLayout(device_group)
self.device_list = QListWidget()
self.device_list.addItems([
"心电监护仪 (ECG-001)",
"血压监护仪 (BP-002)",
"血氧传感器 (SpO2-003)",
"输液泵 (PUMP-004)"
])
device_layout.addWidget(self.device_list)
left_layout.addWidget(device_group)
# 控制按钮
control_group = QGroupBox("系统控制")
control_layout = QVBoxLayout(control_group)
self.start_monitoring_btn = QPushButton("开始监护")
self.start_monitoring_btn.setStyleSheet("QPushButton { background-color: #27ae60; color: white; font-weight: bold; }")
self.stop_monitoring_btn = QPushButton("停止监护")
self.stop_monitoring_btn.setStyleSheet("QPushButton { background-color: #e74c3c; color: white; font-weight: bold; }")
self.stop_monitoring_btn.setEnabled(False)
self.export_data_btn = QPushButton("导出数据")
self.settings_btn = QPushButton("系统设置")
control_layout.addWidget(self.start_monitoring_btn)
control_layout.addWidget(self.stop_monitoring_btn)
control_layout.addWidget(self.export_data_btn)
control_layout.addWidget(self.settings_btn)
left_layout.addWidget(control_group)
left_layout.addStretch()
main_layout.addWidget(left_panel)
# 右侧主要内容区域
right_panel = QTabWidget()
# 实时监护标签页
self.visualization_widget = DataVisualizationWidget()
right_panel.addTab(self.visualization_widget, "实时监护")
# 历史数据标签页
history_widget = QWidget()
history_layout = QVBoxLayout(history_widget)
self.data_table = QTableWidget()
self.data_table.setColumnCount(6)
self.data_table.setHorizontalHeaderLabels([
"时间", "设备ID", "传感器类型", "数值", "单位", "状态"
])
history_layout.addWidget(self.data_table)
right_panel.addTab(history_widget, "历史数据")
# 网络通信标签页
network_widget = QWidget()
network_layout = QVBoxLayout(network_widget)
self.network_log = QTextEdit()
self.network_log.setMaximumHeight(200)
network_layout.addWidget(QLabel("网络通信日志:"))
network_layout.addWidget(self.network_log)
# 消息发送
send_layout = QHBoxLayout()
self.message_edit = QLineEdit()
self.message_edit.setPlaceholderText("输入要发送的消息...")
self.send_message_btn = QPushButton("发送广播")
send_layout.addWidget(self.message_edit)
send_layout.addWidget(self.send_message_btn)
network_layout.addLayout(send_layout)
network_layout.addStretch()
right_panel.addTab(network_widget, "网络通信")
main_layout.addWidget(right_panel)
def setup_menu_bar(self):
"""设置菜单栏"""
menubar = self.menuBar()
# 文件菜单
file_menu = menubar.addMenu("文件")
export_action = QAction("导出数据", self)
export_action.setShortcut(QKeySequence.Save)
export_action.triggered.connect(self.export_data)
file_menu.addAction(export_action)
file_menu.addSeparator()
exit_action = QAction("退出", self)
exit_action.setShortcut(QKeySequence.Quit)
exit_action.triggered.connect(self.close)
file_menu.addAction(exit_action)
# 工具菜单
tools_menu = menubar.addMenu("工具")
settings_action = QAction("设置", self)
settings_action.triggered.connect(self.show_settings)
tools_menu.addAction(settings_action)
# 帮助菜单
help_menu = menubar.addMenu("帮助")
about_action = QAction("关于", self)
about_action.triggered.connect(self.show_about)
help_menu.addAction(about_action)
def setup_tool_bar(self):
"""设置工具栏"""
toolbar = QToolBar()
self.addToolBar(toolbar)
start_action = QAction("开始", self)
start_action.triggered.connect(self.start_monitoring)
toolbar.addAction(start_action)
stop_action = QAction("停止", self)
stop_action.triggered.connect(self.stop_monitoring)
toolbar.addAction(stop_action)
toolbar.addSeparator()
settings_action = QAction("设置", self)
settings_action.triggered.connect(self.show_settings)
toolbar.addAction(settings_action)
def setup_status_bar(self):
"""设置状态栏"""
self.statusBar().showMessage("系统就绪")
# 添加永久状态组件
self.connection_count_label = QLabel("连接数: 0")
self.statusBar().addPermanentWidget(self.connection_count_label)
self.data_count_label = QLabel("数据包: 0")
self.statusBar().addPermanentWidget(self.data_count_label)
def setup_connections(self):
"""设置信号连接"""
# 按钮连接
self.start_monitoring_btn.clicked.connect(self.start_monitoring)
self.stop_monitoring_btn.clicked.connect(self.stop_monitoring)
self.export_data_btn.clicked.connect(self.export_data)
self.settings_btn.clicked.connect(self.show_settings)
self.send_message_btn.clicked.connect(self.send_broadcast_message)
# 网络管理器连接
self.network_manager.data_received.connect(self.handle_network_data)
self.network_manager.connection_status_changed.connect(self.update_connection_status)
# CAN模拟器连接
self.can_simulator.frame_received.connect(self.handle_can_frame)
self.can_simulator.frame_received.connect(self.visualization_widget.update_medical_data)
def start_services(self):
"""启动网络服务"""
# 启动TCP服务器
if self.network_manager.start_tcp_server(8888):
self.tcp_status_label.setText("TCP服务器: 运行中 (端口8888)")
self.tcp_status_label.setStyleSheet("color: green;")
# 绑定UDP套接字
if self.network_manager.bind_udp_socket(9999):
self.udp_status_label.setText("UDP套接字: 已绑定 (端口9999)")
self.udp_status_label.setStyleSheet("color: green;")
def start_monitoring(self):
"""开始监护"""
self.can_simulator.start_simulation()
self.can_status_label.setText("CAN总线: 运行中")
self.can_status_label.setStyleSheet("color: green;")
self.start_monitoring_btn.setEnabled(False)
self.stop_monitoring_btn.setEnabled(True)
self.statusBar().showMessage("监护已启动")
logger.info("监护系统启动")
def stop_monitoring(self):
"""停止监护"""
self.can_simulator.stop_simulation()
self.can_status_label.setText("CAN总线: 已停止")
self.can_status_label.setStyleSheet("color: red;")
self.start_monitoring_btn.setEnabled(True)
self.stop_monitoring_btn.setEnabled(False)
self.statusBar().showMessage("监护已停止")
logger.info("监护系统停止")
@Slot(dict)
def handle_can_frame(self, frame):
"""处理CAN帧数据"""
device = frame.get('device', '')
data = frame.get('data', {})
timestamp = frame.get('timestamp', 0)
# 保存到数据库
for key, value in data.items():
if isinstance(value, (int, float)):
self.database.insert_sensor_data(
device_id=device,
sensor_type=key,
value=value,
unit=self.get_unit_for_sensor(key)
)
# 更新历史数据表格
self.update_data_table()
def get_unit_for_sensor(self, sensor_type: str) -> str:
"""获取传感器单位"""
units = {
'heart_rate': 'bpm',
'systolic': 'mmHg',
'diastolic': 'mmHg',
'spo2': '%',
'pulse': 'bpm'
}
return units.get(sensor_type, '')
def update_data_table(self):
"""更新数据表格"""
data = self.database.get_latest_data("ECG_MONITOR", 20)
data.extend(self.database.get_latest_data("BP_MONITOR", 20))
data.extend(self.database.get_latest_data("SPO2_SENSOR", 20))
# 按时间排序
data.sort(key=lambda x: x['timestamp'], reverse=True)
self.data_table.setRowCount(len(data))
for row, record in enumerate(data):
self.data_table.setItem(row, 0, QTableWidgetItem(str(record['timestamp'])))
self.data_table.setItem(row, 1, QTableWidgetItem(record['device_id']))
self.data_table.setItem(row, 2, QTableWidgetItem(record['sensor_type']))
self.data_table.setItem(row, 3, QTableWidgetItem(str(record['value'])))
self.data_table.setItem(row, 4, QTableWidgetItem(record['unit']))
self.data_table.setItem(row, 5, QTableWidgetItem(record['status']))
@Slot(str, bytes)
def handle_network_data(self, sender: str, data: bytes):
"""处理网络数据"""
try:
message = data.decode('utf-8')
log_entry = f"[{QDateTime.currentDateTime().toString()}] 来自 {sender}: {message}"
self.network_log.append(log_entry)
logger.info(f"网络数据: {sender} -> {message}")
except UnicodeDecodeError:
log_entry = f"[{QDateTime.currentDateTime().toString()}] 来自 {sender}: 二进制数据 ({len(data)} 字节)"
self.network_log.append(log_entry)
@Slot(bool)
def update_connection_status(self, connected: bool):
"""更新连接状态"""
if connected:
self.connection_count_label.setText(f"连接数: {len(self.network_manager.tcp_clients)}")
def send_broadcast_message(self):
"""发送广播消息"""
message = self.message_edit.text().strip()
if message:
self.network_manager.broadcast_message(message)
self.message_edit.clear()
self.network_log.append(f"[{QDateTime.currentDateTime().toString()}] 广播: {message}")
def export_data(self):
"""导出数据"""
filename, _ = QFileDialog.getSaveFileName(
self, "导出数据", "", "CSV文件 (*.csv);;所有文件 (*)"
)
if filename:
try:
# 获取所有数据
all_data = []
devices = ["ECG_MONITOR", "BP_MONITOR", "SPO2_SENSOR"]
for device in devices:
data = self.database.get_latest_data(device, 1000)
all_data.extend(data)
# 写入CSV文件
import csv
with open(filename, 'w', newline='', encoding='utf-8') as csvfile:
fieldnames = ['timestamp', 'device_id', 'sensor_type', 'value', 'unit', 'status']
writer = csv.DictWriter(csvfile, fieldnames=fieldnames)
writer.writeheader()
for record in all_data:
writer.writerow({
'timestamp': record['timestamp'],
'device_id': record['device_id'],
'sensor_type': record['sensor_type'],
'value': record['value'],
'unit': record['unit'],
'status': record['status']
})
QMessageBox.information(self, "导出成功", f"数据已导出到: {filename}")
logger.info(f"数据导出成功: {filename}")
except Exception as e:
QMessageBox.critical(self, "导出失败", f"导出数据时发生错误: {e}")
logger.error(f"数据导出失败: {e}")
def show_settings(self):
"""显示设置对话框"""
dialog = SettingsDialog(self)
if dialog.exec() == QDialog.Accepted:
QMessageBox.information(self, "设置", "设置已保存,部分设置需要重启应用后生效。")
def show_about(self):
"""显示关于对话框"""
QMessageBox.about(self, "关于",
"嵌入式医疗设备控制系统\n\n"
"基于Qt/PySide6开发\n"
"支持CAN FD总线通信\n"
"实时数据监护与分析\n\n"
"版本: 1.0.0"
)
def closeEvent(self, event):
"""窗口关闭事件"""
reply = QMessageBox.question(
self, "确认退出", "确定要退出系统吗?",
QMessageBox.Yes | QMessageBox.No,
QMessageBox.No
)
if reply == QMessageBox.Yes:
# 停止所有服务
self.can_simulator.stop_simulation()
# 关闭数据库连接
if self.database.connection:
self.database.connection.close()
logger.info("应用程序退出")
event.accept()
else:
event.ignore()
def main():
"""主函数"""
app = QApplication(sys.argv)
# 设置应用程序信息
app.setApplicationName("嵌入式医疗设备控制系统")
app.setApplicationVersion("1.0.0")
app.setOrganizationName("Embedded Medical Solutions")
# 设置样式
app.setStyleSheet("""
QMainWindow {
background-color: #f5f5f5;
}
QGroupBox {
font-weight: bold;
border: 2px solid #cccccc;
border-radius: 5px;
margin-top: 1ex;
padding-top: 10px;
}
QGroupBox::title {
subcontrol-origin: margin;
left: 10px;
padding: 0 5px 0 5px;
}
QPushButton {
background-color: #5D5CDE;
border: none;
color: white;
padding: 8px;
border-radius: 4px;
font-weight: bold;
}
QPushButton:hover {
background-color: #4a49c7;
}
QPushButton:pressed {
background-color: #3f3eb5;
}
QPushButton:disabled {
background-color: #cccccc;
color: #666666;
}
QTableWidget {
gridline-color: #e0e0e0;
background-color: white;
}
QTableWidget::item {
padding: 8px;
}
QTabWidget::pane {
border: 1px solid #cccccc;
background-color: white;
}
QTabBar::tab {
background-color: #e0e0e0;
padding: 8px 16px;
margin-right: 2px;
}
QTabBar::tab:selected {
background-color: #5D5CDE;
color: white;
}
""")
# 创建主窗口
window = MainWindow()
window.show()
logger.info("应用程序启动")
# 运行应用程序
sys.exit(app.exec())
if __name__ == "__main__":
main()
559

被折叠的 条评论
为什么被折叠?



