从零写一个工控多协议通讯库(五):画面组态——从写死一个监视器,到用组态把它拼出来

发布时间:2026/8/16 3:36:13
从零写一个工控多协议通讯库(五):画面组态——从写死一个监视器,到用组态把它拼出来 本文首发于我的博客talkplc.com系《从零写一个工控多协议通讯库》系列第五篇。转载请注明出处。第四篇接第二种协议时立过一个标准新协议进来框架和界面一行不改。S7 通过了考试。但界面本身一直是块心病——协议树、点表、收发帧监视全是写死在MainWindow里的。这篇把界面也变成数据原来是我写一个监控软件现在监控软件的页面由组态软件拼出来。项目代号 talkplc个人时间与设备自研实现不碰任何厂商代码。起点一份代码即界面的监视器talkplc 的监视窗口长这样左边协议树右边串口参数、八列点表、收发帧监视顶上一颗连接状态灯。功能没毛病但每加一个显示需求都要改 C 重新编译。界面是代码不是数据。组态软件WinCC、组态王、Ignition 这类的路子正好相反界面是一份配置文件运行器加载它、按配置采集和显示。好处人人都知道。但真下手做的时候问题一个一个冒出来。设计器画的东西运行器怎么认这需要一个双方都认的东西也就是契约。构件数值、仪表、按钮这些怎么加才不用改一堆地方这个问题协议层已经答过一次注册表。还有表格、设备列表这种长得像表的东西是一个一个写还是有别的办法第一步契约先行别管界面设计器和运行器是两个角色工控惯例是组态环境和运行环境分开。但它们之间只需要一个东西工程文件。所以第一刀切在数据上。工程 project connections[] screens[] connections[i] 一个 tp_conn_config 一个 id协议/链路/点表全是已有的 screens[i] 画布尺寸 widgets[]构件数组 widgets[j] type rect 绑定(conn id 点名) 各自属性构件绑定用名字连接 id 加点名而不是索引稳定、可读手改 JSON 也不容易错运行时再解析成索引。旧的单连接配置文件自动视作一个iddefault的连接向前兼容白送。有了契约程序形态反而是小事。一个 exe 两个模式--design进设计器产出工程--screen进运行器消费工程。设计器只产不跑设计态不连设备构件画占位运行器只跑不编辑。将来要拆成两个程序或者把运行器下沉到嵌入式LVGL契约一个字不用改。内核照旧零改。运行器核心tp_runtime是个纯 C 引擎吃一份工程每连接自动开一个会话每拍把值递给渲染层。Qt 只是它的一个前端。构件自己报属性属性面板谁也不认识设计器里最容易被低估的是属性面板。我的做法是让每个构件自己声明有哪些属性绑定、量程、颜色、动作面板只负责把声明渲染成编辑行、把编辑写回去QListWidgetPropGaugeItem::typeProps()const{QListWidgetPropL;LwpMake(unit,单位,WP_Text)wpMake(min,最小,WP_Real)wpMake(max,最大,WP_Real);returnL;}面板是通用的加构件不用改面板。这句话后来被我自己打脸了一次。画面跳转按钮有个 bug属性面板显示目标画面画面1存盘后却是空的跳转永远没反应。查下来是两个不起眼的事凑在一起——下拉框显示着第一项但这个值从没被写进构件。你看到的和你存下来的不是一回事。修法是把所见即所存定成面板的硬规则值为空时把显示着的默认项真正提交进去。这个 bug 比功能本身值钱。通用 UI 的坑往往就藏在显示和数据的缝隙里。加构件和加协议一个待遇前四篇最得意的设计是协议注册表tp_registry名字到工厂加协议等于加一行框架核心零改。那构件凭什么例外// gui/screen/items/TableItem.cpp 文件尾staticScreenItem*makeTable(ints,intw,consttp_screen_widget_td){returnnewTableItem(s,w,d);}namespace{structTableRegistrar{TableRegistrar(){ItemInfo ii{table,表格,70,360,260,paintTableIcon,makeTable};itemRegistryAdd(ii);}}tableReg_;}每个构件一个自包含文件文件尾一个静态注册器把自己登记进ItemRegistry登记的内容是类型名、调色板标签、默认尺寸、图标、工厂。设计器的调色板和工厂只读注册表。工厂 switch、调色板数组、图标 if/else这些我在第一版里亲手写过的东西全都不存在了。加一个构件 加一个文件 CMake 一行。和加协议一个待遇。一段弯路专门构件的坑接下来我犯了这篇里最大的错值得原样记下来。做设备管理页左边设备列表右边选中设备的点表和参数的时候我按老习惯写了三个专门构件类TableItem点表四列写死、DeviceListItem设备列表、DevParamsItem参数卡带编辑按钮。每个都要新类、工厂分支、调色板注册、图标、默认尺寸。注册表明明已经把成本降到加一个文件了我还在往一个文件里堆类。手上有了新工具脑袋还在旧习惯里。想通之后全部推倒。点表、设备列表、参数卡本质是同一种东西一张表只是数据源不同。于是通用表格有了source属性conn是整连接点表可以子集选点devices是设备清单行点击可以选中设备devattr是选中设备的属性卡。列用columns配行点击动作用row_action配。参数卡上那个编辑参数按钮就是普通按钮构件的一个动作actionparams。{type:table,source:devices,columns:dot,id,proto,row_action:select,rect:[40,96,300,660]}上面这七行就是原来 DeviceListItem 那 150 行类的全部。后来我给自己定了个规矩再遇到表状的东西先想能不能用通用表格配出来能配就不写类。让右侧跟着左侧走master-detail 还差一口气左边点了一台设备右边的点表、仪表怎么跟着换给构件绑定加一个特殊值conn意思是跟随运行器当前选中的设备。选中状态放在tp_runtime里绑定解析每次即时求值解析成选中的连接 id。任何构件绑上就自动联动跨设备同名点位比如都叫 temp无缝跟随不同名的显示无效。参数编辑也顺下来了。画面上一个编辑参数按钮绑点了弹出该设备当前值预填的表单。表单字段来自协议清单protocols.json每种协议声明自己有哪些可编辑参数又是一层配置。确定之后改配置、断开重连、自动存回工程 JSON。运行中的监视页面可以改波特率、换 IP改完状态灯闪一下连接中就回来了。验收把监视器拼回来说再多验收标准就一条原来写死的那个监视页面现在能用设计器拼出来。标题是文本构件点表是通用表格绑连接写入行是输入框绑点位仪表盘是三个 gauge。原来几百行 C 的界面现在是四十来行 JSON。点表里每一行的值和状态是活的仪表的指针在动。再往上叠一层就是完整的设备管理页三台设备三种协议S7、Modbus RTU、Omron NJ全部跑内置仿真从站点左边的行右边整块跟着换参数卡上改个采集周期重连生效存盘留痕。而它本身也是设计器里拖出来的没有硬件怎么验证老规矩sim链路三台仿真从站一起跑画面上的值就是活的。纯 C 的契约层工程读写、绑定解析、选中、重连在 ctest 里跑一遍全绿GUI 层人工过——画面来回切几轮看跳转设备列表点几行看右侧跟不跟参数改个采集周期看状态灯闪一下连接中再回来。没有 PLC 也能把整条画面逻辑过一遍这套方法从第一篇用到现在。接下来四篇立住了数据面协议是外挂的这一篇立住了表达面。工程 JSON 是设计器和运行器之间唯一的东西构件自描述加注册表加构件和加协议一样便宜表状界面是配置不是类型这条弯路比任何功能都值钱一个字符master-detail 免费送。后面排得上号的趋势曲线window_s字段从第一版起就躺在契约里等着被用、设计器里一键生成设备页、把纯 C 运行器搬到 LVGL 上。契约不变换个前端而已。每加一层都回来对照一次改动越小说明当初的抽象越对。系列前四篇① 架构与传输抽象 · ② Modbus TCP 与重构 · ③ 数据构件仪表盘 · ④ 西门子 S7