AZBIL 温控项目协作复盘:从协议整理到双路线 Dashboard 演进

复盘一个 AZBIL C2/C3 温控项目如何从协议澄清、Python 标准库抽象、YAML 工艺配置,逐步演进到两套 Dashboard 方案。

写在前面

最近做了一个 AZBIL C2/C3 温控项目,原本只是想把设备通讯先接起来,结果一路演进下来,最后沉淀成了:

  • 一份协议整理文档
  • 一个可复用的 Python 通讯库
  • 一套 YAML 工艺配置机制
  • 一套 CLI 调试工具
  • 两条不同技术路线的 Dashboard

回头看,这次最有价值的不是“做完了多少功能”,而是整个开发过程越来越像产品化沉淀,而不是一次性脚本。

这篇文章主要复盘这次协作中最值得保留的开发思路。


一、第一步不是写代码,而是先确认资料是否可靠

项目一开始其实就踩了一个很典型的坑:

最先拿到的 PDF 并不是正式说明书,而是样本文件。

这个问题如果没及时发现,会直接导致后续实现建立在错误前提上。尤其对工控设备来说,样本文件和正式说明书的差别非常大:

  • 样本文件适合看产品概览
  • 正式说明书才能作为协议实现依据

比如这次项目里,真正关键的内容都必须回到正式说明书确认:

  • CPL 私有协议帧结构
  • Modbus ASCII / RTU 功能码
  • 校验方式
  • 时序要求
  • RAM 地址和非易失性地址的区别
  • 程序段访问区的真实地址

这一阶段最大的收获不是技术,而是一个简单但经常被忽略的原则:

资料源不可靠时,越早承认,返工越少。


二、先把“地址语义”讲清楚,再做抽象

这次还有一个特别值得记住的小细节。

最初给到的一张现场地址截图里,7190 / 7191 被标记成了 SP1 / Time1。但按原厂手册,真正的对应关系是:

  • 7178 / 7179:第 1 段
  • 7184 / 7185:第 2 段
  • 7190 / 7191:第 3 段

也就是说,截图里的命名和原厂编号并不一致。

后来进一步沟通后,这个差异才彻底说通:

  • 从原厂编号看,7190 / 7191 的确是第 3 段
  • 从现场工艺习惯看,真正给人工调的主工艺也是从第 3 段开始
  • 第 1、2 段更多是保护性缓升段,首次调好之后就不再给普通用户改

这件事直接影响了后续所有设计:

  • YAML 配置从第 3 段开始更符合现场思维
  • 程序曲线可视化也应该把第 3 段作为主工艺起点
  • 程序段编辑和帮助说明不能简单照着截图命名

所以这一步其实是在做一件非常基础、但非常关键的事:

先把设备世界里的真实语义理顺,再谈代码里的抽象。


三、先做标准库,不要一上来就做界面

协议和地址理顺后,项目的交付目标很快被收敛成了两块:

  1. 协议整理文档
  2. 标准通讯库

这一步的判断我现在觉得非常正确。

因为对于这种项目,真正长期有价值的,不是某个页面,而是底层这几层能力:

  • 总线层
  • 设备层
  • 寄存器层
  • 枚举层
  • 协议适配层

最后沉淀出来的结构大致就是这样:

  • bus.py:串口和总线抽象
  • device.py:单台设备操作对象
  • registers.py:常用寄存器和地址公式
  • enums.pyRUN/READYAUTO/MANUAL 等语义化枚举
  • protocols/ModbusCPL
  • cli.py:现场调试入口

为什么这一步重要?

因为后面不管是 Web、桌面端,还是其他 Python 项目集成,复用的都不是界面,而是这些标准层。

换句话说:

标准层做对了,UI 只是换皮;标准层没做好,后面接什么都容易返工。


四、把工艺参数从代码里抽出来,交给 YAML

项目推进到一半时,一个很重要的需求变得越来越清晰:

用户真正关心的,不是某个寄存器怎么写,而是:

  • 不同项目能不能换一套控温曲线
  • 不同工艺能不能不改代码
  • 调机完成后能不能固化下来,避免现场乱改

于是后面就把“设备通信”和“工艺配置”彻底拆开了。

最终的设计是:

设备层负责什么

  • 串口
  • 协议
  • 从站
  • 寄存器读写
  • 状态读取

工艺层负责什么

  • 程序号
  • 起始段
  • 各段 SP / 时间 / PID组
  • PID 参数
  • 需要初始化写入的补充寄存器

对应的 YAML 结构也就顺理成章了:

device:
  protocol: modbus_rtu
  port: COM3
  slave: 1
  baudrate: 9600
  parity: E
  stopbits: 1
  timeout: 2.0

profile:
  name: example-oven
  description: Main heating profile
  program_no: 1
  active_segment_start: 3

pid_groups:
  - group: 1
    kp: 120
    ti: 180
    td: 45

segments:
  - segment: 3
    sp: 120
    time: 300
    pid_group: 1
  - segment: 4
    sp: 180
    time: 600
    pid_group: 1

这一步之后,整个项目的可维护性明显上了一个台阶:

  • 不同项目只换 YAML
  • 首次调试后可以固化配置
  • 后续可做“保存配置 / 下发配置 / 校验配置”
  • 方案可以归档和追溯

如果说前面是在做“驱动”,那从这里开始,已经是在做“平台基础设施”了。


五、CLI 先行,是最稳妥的现场验证方式

在做图形界面之前,先补 CLI,这个顺序后来被证明非常值。

原因很简单:

  • CLI 是最薄的验证层
  • 出问题时最容易看出是设备问题、协议问题还是逻辑问题
  • 它本身也是非常实用的现场工具

后面逐步补上的 CLI 能力包括:

  • read
  • read-words
  • write
  • scan
  • live
  • program-status
  • dump
  • apply-profile
  • verify-profile

其中 dump 很有意思,它其实相当于一个“纯文本版控制台”。

也是在它出现以后,很多 UI 设计的取舍才变得更清楚:

  • 哪些数据值得一屏盯着看
  • 哪些设置不应该占主界面
  • 哪些帮助说明应该单独放一页

这一步带给我的一个经验是:

图形界面之前先做 CLI,不是多此一举,而是帮 UI 探路。


六、第一条界面路线:React + FastAPI

第一版 Dashboard 选择的是:

  • 前端:React + Vite
  • 后端:FastAPI

为什么先选它?

因为它特别适合快速验证交互结构:

  • 页面怎么分
  • 哪些卡片该保留
  • 程序曲线怎么画
  • 程序段怎么编辑
  • 配置方案怎么选择
  • PID 放哪里更合适

这一版的最大价值,不在于最终部署,而在于它帮我们迅速把“界面逻辑”试出来了。

但它也暴露了几个很真实的问题。

1. 一屏放太多内容,主次不清

最早的版本几乎把所有东西都塞在一页里:

  • 实时数据
  • 程序曲线
  • 配置编辑
  • 串口设置
  • PID
  • 说明文字

结果就是页面很满,但并不好用。

后来改成按功能分屏后,体验一下就顺了很多:

  • 实时总览
  • 程序曲线
  • 系统设置
  • 帮助说明

2. 工艺配置不应该被塞进右侧窄栏

一开始把“程序配置编辑”放在侧栏里,结果输入框、表头、按钮都被挤坏了。

后来调整成:

  • 顶部:曲线预览 + 方案选择
  • 中间:程序段表
  • 下方:整行宽配置编辑

这个改动让我很认同一个简单判断:

真正的主任务,不能被做成附属区块。

3. 配置文件编辑和程序段参数设置本质重复

这也是一个典型的“越做越发现重复”的例子。

如果用户已经在编辑配置文件:

  • 再单独做一套程序段参数卡片

那实际上就是两套入口。

后来就统一成了一个思路:

  • 只保留“配置方案编辑”
  • 编辑后可直接“保存”
  • 或者“保存并下发”

这样逻辑一下就清楚了。


七、第二条界面路线:FastAPI 单体版

虽然 React + FastAPI 很适合做交互验证,但很快就遇到了一个特别现实的问题:

用户主要在 Windows 电脑端长期使用,希望部署更轻,不想额外依赖 Node.js。

这个反馈让我意识到:

界面技术栈不该只看“开发舒适度”,还要看“交付场景”。

所以后来又开了第二条路线:

  • FastAPI
  • 原生 HTML / CSS / JS
  • Chart.js
  • 内嵌静态资源
  • 单体部署

最后做成了一个很清晰的模型:

  • pip install
  • azbil-dashboard
  • 浏览器访问本机地址

这条路线的好处非常直接:

  • 不依赖 Node 运行环境
  • 不依赖外网 CDN
  • 更适合 Windows 现场电脑
  • 更像一个单机上位机工具

这让我越来越觉得:

不是技术越“现代”越好,而是越贴近实际部署场景越好。


八、后期最重要的优化,其实不是视觉,而是语义统一

项目做到后面,真正决定“能不能好用”的,反而不是配色或布局,而是语义是否统一。

这里有几个特别典型的点。

1. 配置文件名和配置名称不一致,会让用户混乱

一开始界面里同时存在:

  • 配置文件名
  • profile.name

如果另存为后这两个不同步,用户会非常容易困惑:

  • 我现在改的是哪个文件?
  • 当前方案到底叫什么?

最后的简化规则是:

  • 当前编辑对象就是当前选中的配置文件
  • 另存为时默认同步显示名
  • 历史 YAML 中不一致的也统一清理

这个问题看起来小,但它解决的是认知负担。

2. PID 不该抢占普通用户主流程

从现场使用视角看,普通用户最关心的是:

  • 当前温度
  • 目标温度
  • 程序段
  • 剩余时间
  • 控温曲线

而不是 KP / TI / TD

所以后面 PID 被后置到了“系统设置”页,作为工程参数处理。

这是一个很典型的角色分层:

  • 普通操作
  • 工艺配置
  • 工程参数

3. 时间单位必须统一

这个坑特别值得单独记一笔。

一开始页面里的时间显示其实不一致,出现过:

  • 300
  • 300s
  • 5h 0m

这些表现。

从程序逻辑上看或许都能解释,但对用户来说非常容易误导。

最后统一后的原则非常明确:

  • 整个 Dashboard 统一按 分钟
  • 不再自动折算成小时
  • 直接显示 300 min
  • 曲线、程序段表、帮助说明、文档同步一致

这件事再次说明:

工业界面最怕的不是丑,而是同一个量出现多种语义。


九、这次最值得保留的方法论

如果把这次协作抽象成一套以后还能复用的方法,我最想保留的是下面几条。

1. 先协议,后界面

顺序上一定是:

  1. 先确认资料源
  2. 先整理协议
  3. 先做标准库
  4. 先抽配置机制
  5. 先做 CLI
  6. 最后做 Dashboard

这个顺序几乎决定了返工量。

2. 先做可复用,再做可视化

真正长期有价值的不是页面,而是:

  • bus
  • device
  • registers
  • profile
  • cli

UI 只是这些能力的一种承载形式。

3. 可以用两条技术路线分别解决“验证”和“交付”

这次两条分支特别有代表性:

  • codex/dashboard-ui
    • 更适合验证交互结构
  • codex/dashboard-fastapi-static
    • 更适合最终交付与部署

这不是浪费,而是一种高效迭代方式:

  • 先验证方向
  • 再收敛交付模型

4. 用户觉得“别扭”的地方,往往最值得优先处理

这次很多关键优化都不是技术错误,而是用户体验层面的“不顺手”:

  • 一屏太挤
  • 曲线太高
  • 配置编辑放错位置
  • 说明文字太抢眼
  • 文件名和配置名不一致
  • 时间单位不统一

这些反馈如果忽略,系统即使功能完整,也很难真正落地。


十、最后沉淀下来的,其实是一套基础设施

如果只看最终产出,这次项目沉淀下来的东西已经不只是某个控制页面了,而是一整套温控项目基础设施:

  • 协议文档
  • 使用文档
  • Python 通讯库
  • 寄存器目录
  • 枚举与状态封装
  • YAML 配置机制
  • CLI 调试工具
  • React 版 Dashboard
  • FastAPI 单体版 Dashboard

从这个角度看,这次协作真正完成的是:

从设备协议理解,到标准层抽象,再到配置驱动和界面落地的一整条链路。


结语

回头复盘,这次最让我认可的,不是“做了很多功能”,而是整个开发思路越来越稳:

  • 从样本文件纠偏到正式说明书
  • 从寄存器堆砌到标准层抽象
  • 从手工设置到 YAML 配置化
  • 从单页堆功能到按角色与任务分屏
  • 从技术偏好到部署场景优先

如果以后再做类似的工控或上位机项目,我仍然会优先坚持这套思路:

先厘清协议,先做标准层,先让系统可复用,再去追求界面的完整和漂亮。

这次项目最大的收获,也许并不是“做成了一个 Dashboard”,而是把一套能反复复用的开发方法走通了。