简介:组态王6.5源代码是一份工业自动化监控软件的早期程序包,面向组态软件开发者、工业控制工程师及.NET技术学习者,用于研究设备监控、数据采集与Web远程控制系统的实现思路。资源共202个文件,压缩包仅169KB,以C++源文件(cpp/h)和Visual C++工程文件(dsw/dsp/rc)为主体,并包含bmp/ico界面位图、rtf/hlp帮助文档、bat构建脚本等,呈现典型的MFC工程结构;内容预览中还能看到启动画面位图、帮助构建脚本等界面与文档配套资源。已有458人学习下载。通过阅读源码,可以理解图形化组态界面如何与ASP.NET Web服务结合,包括实时数据展示、ADO.NET数据库通信、事件驱动处理、多线程并发以及安全控制等模块;工程文件与资源脚本也有助于快速梳理从底层控制逻辑到浏览器端监控的分层设计思路,适合源码分析、二次开发或相关课程设计参考。
1. 组态王6.5源代码:先分清是工程源码还是驱动源码
组态王6.5源代码这个检索词背后,问的人十有八九是要一个能直接打开的监控工程,而不是去读它的PLC驱动源码。这份资源是一套基于组态王6.5的完整监控工程源码,从数据词典、用户画面、命令语言到SQL转储配置都齐全,场景落在某水处理监控Demo上,覆盖泵组启停、液位显示、流量累计和基础报警。适合两类人:一类是刚接手老产线的工程师,要把现场跑了好几年的6.5工程看懂、改参数;另一类是正在搭Web远程监控的人,6.5没有现成Web API,怎么把数据表搬到ASP.NET页面需要一条可靠通道。这套工程正好把两端都打通了。
2. 组态王6.5的工程骨架:变量表、画面与命令语言三件套
一套6.5工程拿到手,别急着开运行系统。先看三个地方:数据词典里的变量表、画面文件里的图元绑定、命令语言里的脚本触发。这三样是一套组态工程的骨架,任何一个对不上,运行系统里就是数据为0、画面不动、脚本不跑。这一章把每一样拆开讲,后面的Web对接全部建立在这三层之上。
2.1 变量体系:内存变量与I/O变量的分工
组态王6.5的变量全部维护在“数据词典”里。变量分两大类:一类是内存变量,数据在工控机内部产生,不需要外部设备参与;另一类是I/O变量,绑定PLC、仪表、变频器这些外部设备的寄存器。新手最容易犯的错,是把内部计算量定义成I/O变量,结果设备一离线,整个画面都跟着报错。
| 变量类型 | 典型用途 | 数据来源 |
|---|---|---|
| 内存离散 | 按钮状态、内部开关量 | 工控机内部 |
| 内存整数 | 计数器、设定值、模式切换 | 工控机内部 |
| 内存实数 | PID中间量、计算值 | 工控机内部 |
| 内存信息型 | 操作员姓名、报警文本 | 工控机内部 |
| I/O离散 | 外部开关量输入/输出 | PLC或仪表寄存器 |
| I/O整数 | 电机转速、外部计数 | PLC或仪表寄存器 |
| I/O实数 | 压力、液位、流量模拟量 | PLC或仪表寄存器 |
| I/O信息型 | 仪表返回的字符串信息 | 串口/网口仪表 |
定义I/O变量时,关键在“连接设备”和“寄存器”两个框。连接设备指向你在“设备配置”里建好的PLC驱动,寄存器格式由具体驱动决定,比如某款PLC的保持寄存器写成“D100”,另一款写成“40001”,搞混了采集值永远是0。定义完变量后,打开“测试”按钮可以直接看到原始值,这一步能过滤掉一半的现场通讯问题。
内存变量虽然不接设备,但它是脚本逻辑的核心。这套水处理Demo里,泵组的联锁逻辑全部用内存离散加内存整数实现,I/O变量只负责把PLC数据拿进来,再通过动画连接把内存变量画到画面上。这么分层的好处是,PLC点位变更时只改I/O变量,画面和脚本可以不动。
2.2 画面开发:图元绑定与动画连接
画面文件以.pic后缀存在工程目录下,开发系统里双击就能编辑。图元是静态的,要动起来必须做“动画连接”:双击图元,弹出动画连接对话框,把某个属性绑定到一个变量上。常用连接类型有填充、缩放、旋转、闪烁、隐藏和文本输出,一个图元可以同时绑定多个属性。
液体罐的液位显示,就是用一个矩形图元绑定“进水液位”变量,范围设成0到100,填充方向从下往上。这样变量值变,画面上的蓝色填充高度跟着变。参数不复杂,但要注意两点:第一,变量变化范围要跟PLC的量程一致,PLC里液位量程是0到500厘米,画面范围也设0到500,否则显示到一半就满了;第二,填充百分比的计算方式是(当前值-最小值)/(最大值-最小值),负值或越界会直接不显示。
文本输出这块容易被忽略。模拟量显示框绑定“进水压力”变量后,在动画连接里要选“模拟量输出”,还要设置显示格式。工业现场习惯看两位小数,格式串写成“999.99”,整数位数不够时会显示成“#####”,这是个玄学问题,其实是格式串宽度不够,把位数放宽就行。另外6.5的老图元在缩放时容易变形,推荐直接用工具箱里的标准图元库,别自己拼管线。
2.3 命令语言:三种常用触发方式与语法边界
组态王6.5的命令语言不是独立的编程语言,是一套类C脚本,有if、else、for循环,支持变量直接引用,语法比C宽松很多,但宽松也意味着写错不报错。命令语言按挂载位置分好几类,实际工程里90%用三种:应用程序命令语言、数据改变命令语言、画面命令语言。
应用程序命令语言在运行系统启动时执行,适合做初始化;画面命令语言在画面打开和关闭时触发,适合做画面私有变量复位;数据改变命令语言最常用,绑定一个变量,变量值一变,脚本就执行一遍。下面这段是这套工程里泵联锁的简化版:
// 数据改变命令语言,绑定变量:进水液位 // 液位超过85%时启动备用泵,回落至80%后停止,带2%滞环防止频繁启停 if( \本站点\进水液位 > 85.0 ) { \本站点\备用泵 = 1; \本站点\报警文本 = "进水液位高"; } else if( \本站点\进水液位 < 80.0 ) { \本站点\备用泵 = 0; }这个脚本的触发条件是“进水液位”这个变量的值发生变化,不管变化多少,只要变了就执行。所以PLC那边采集频率越高,脚本执行越频繁。如果PLC的模拟量通道默认死区设置太大,液位缓慢上升时PLC不会上报新值,脚本就不触发,这是现场常见的“看着该动却不动”的原因。解决办法是到设备驱动配置里把“数据变化死区”调小或改成定时采集。
命令语言里引用变量统一用反斜杠开头,格式是\站点名\变量名,站点名默认是“本站点”,在工程浏览器的站点配置里可以改。写脚本时注意别少了开头的反斜杠,少了之后6.5在编译时报“变量未定义”,但这个报错经常被忽略,运行系统照样起,只是脚本不执行。语法层面6.5支持if(...)、for(...)、while(...)循环,不支持数组,字符串处理能力也很弱,要用字符串拼接,只能拿信息型变量做简单赋值,处理不了复杂逻辑。
3. 对接ASP.NET做Web监控:ODBC转储与反向控制的落地路径
这套工程要解决的实际问题是:人在中控室看组态王没问题,但设备负责人要在办公室用浏览器看实时数据和历史趋势。组态王6.5自带的Web发布功能不是不能用,而是依赖IIS版本和ActiveX控件,放到新系统上经常加载失败。这里我用的是另一条路:组态王把数据通过ODBC转储到SQL Server,ASP.NET Web程序读库展示,反向控制再通过数据库回写来完成。
3.1 为什么选ODBC转储:6.5没有直接的Web API
组态王6.5这个年代,还没有内置的HTTP接口或WebSocket推送,它向上层系统喂数据的标准方式是“SQL访问管理器”。这套功能本质上是ODBC客户端,能把变量值按你自己的节奏写入任意支持ODBC的数据库,比如SQL Server。写入频率完全由脚本控制,可以做成1秒一次,也可以做成5秒一次,非常灵活。
相比Web发布,ODBC转储对运行环境干净得多:组态王机器只需要有ODBC驱动和数据库连接串,不需要装IIS,不需要开Web端口,也不涉及ActiveX控件。数据库只要在局域网内可达,ASP.NET应用放到独立的Web服务器上就可以。数据库和应用分离的好处是,改网页逻辑时根本不用碰工控机,风险小很多。
3.2 配置SQL转储:建表、连接与定时入库
先在SQL Server里建一张实时数据表。这套Demo用的表结构很简单,只存点名、值和入库时间:
CREATE TABLE RealtimeData ( Id INT IDENTITY(1,1) PRIMARY KEY, TagName NVARCHAR(50) NOT NULL, TagValue FLOAT NULL, InsertTime DATETIME NOT NULL DEFAULT GETDATE() ); CREATE INDEX IX_RealtimeData_TagName_Time ON RealtimeData(TagName, InsertTime DESC);时间字段用数据库默认值,组态王那边插入时不用管时间,省掉字符串格式转换的麻烦。索引建在点名的入库时间上,Web端查“最新一条”会非常快。
组态王侧在工程浏览器的“SQL访问管理器”里新建一个连接,连接名任意,比如DeviceID,连接串指向刚才的数据库。然后在数据词典里建一个1秒钟周期变化的内存整数变量TimerCnt,用应用程序命令语言做连接初始化,再用数据改变脚本做定时插入:
// 应用程序命令语言,启动时执行一次 SQLConnect( DeviceID, "Provider=SQLOLEDB.1;Server=127.0.0.1;Database=KingViewDB;User ID=sa;Password=your_password;" ); // 数据改变命令语言,绑定变量 TimerCnt // 每5秒插入一条当前值记录 if( \本站点\TimerCnt % 5 == 0 ) { SQLInsert( DeviceID, "RealtimeData", "TagName,TagValue", "\本站点\TagName, \本站点\进水压力" ); }这里TagName是一个内存字符串变量,在脚本里提前赋值为“进水压力”,这样一条SQLInsert可以供多个变量共用,靠改TagName的值区分写进哪一行。TimerCnt取模控制频率,改成10就是每10秒一条,按现场需求调,千万别用1秒一次的频率写历史表,数据库会膨胀得很快。
3.3 ASP.NET Core轮询接口:实时值查询与JSON输出
Web端用ASP.NET Core写一个只读接口,前端每两秒轮询一次最新值,数据量小,实现成本最低。建一个Web API控制器,用SQL参数化查询取每个点名的最新一条记录:
// Controllers/RealtimeController.cs using Microsoft.AspNetCore.Mvc; using Microsoft.Data.SqlClient; [ApiController] [Route("api/realtime")] public class RealtimeController : ControllerBase { private readonly string _conn = "Server=127.0.0.1;Database=KingViewDB;User Id=sa;Password=your_password;" + "TrustServerCertificate=True;"; [HttpGet("latest/{tagName}")] public async Task<IActionResult> Latest(string tagName) { await using var conn = new SqlConnection(_conn); var sql = "SELECT TOP 1 TagValue, InsertTime FROM RealtimeData " + "WHERE TagName = @tag ORDER BY InsertTime DESC"; await using var cmd = new SqlCommand(sql, conn); cmd.Parameters.AddWithValue("@tag", tagName); await conn.OpenAsync(); await using var reader = await cmd.ExecuteReaderAsync(); if (!await reader.ReadAsync()) return NotFound(new { tagName, message = "no data" }); var value = reader.GetDouble(0); var time = reader.GetDateTime(1); return Ok(new { tagName, value, time }); } }查询里用TOP 1配合时间倒序索引,取最新一条。参数化查询是必须的,TagName直接拼接字符串会有注入风险,虽然这是内网系统,但养成习惯没坏处。接口返回的时间是SQL Server服务器本地时间,跟浏览器时区不一致时,前端要做一次转换,或者后端直接返回UTC。
前端用fetch加Date.now()作为查询参数,能绕开浏览器对相同URL的缓存,这个在4.4节还会展开。如果前端和API不在同一个站点下,记得在Program.cs里配CORS,只允许内网前端域名访问,别用AllowAnyOrigin()。
3.4 反向控制:网页指令回写组态王的三条路
只读监控满足不了全部需求,现场经常要在Web端远程启停泵。6.5没有直接提供对外写变量的服务接口,常见做法有三条路,稳定性天差地别。
第一条是用DDE或NetDDE,6.5这个时代支持DDE,配置成服务端后Excel或第三方程序可以直接读写变量,但DDE是单机机制,NetDDE在现在的Windows版本上基本被砍掉了网络共享支持,内网环境下经常连不上,踩坑概率极高。
第二条是文件交换,组态王脚本定时读一个本地文本文件,Web端把指令写进文件,优点是实现简单,缺点是文件并发读写会丢数据,而且工控机文件系统不能乱碰。
第三条最稳,也是我现在一直用的:复用数据库通道。Web端往指令表插入一行记录,组态王侧用高频率脚本轮询这张表,取到未执行指令后写变量,再把指令状态改成已执行。回写脚本挂在TimerCnt变量上,每秒轮询一次:
// 数据改变命令语言,绑定变量 TimerCnt if( \本站点\TimerCnt % 1 == 0 ) { SQLSelect( DeviceID, "ControlCommand", "CmdId,CmdValue", "CmdStatus=0", "\本站点\CmdId, \本站点\CmdValue" ); if( \本站点\CmdId > 0 ) { // CmdValue为1则开阀,为0则关阀 \本站点\阀门指令 = \本站点\CmdValue; SQLUpdate( DeviceID, "ControlCommand", "CmdStatus", "1", "CmdId=\本站点\CmdId" ); } }这条路的坑在指令确认时序:Web端要等组态王把CmdStatus更新成1之后,再提示用户操作成功,否则会误报。组态王端要注意SQLSelect查出来的值如果有多条,它只会取结果集第一行,轮询频率高时指令表会堆积,Web端写入前要检查同一点位是否存在未执行指令。
4. 组态王6.5避坑指南:驱动离线、历史库缺端的四次翻车记录
6.5这套东西能跑十几年,稳定性没得说,但它的报错方式极其隐蔽,经常是“啥都正常就是数据不对”。这一章把我在这个工程上踩过、也帮别人处理过的四类问题列出来,每条都是先现象后原因再给解决,可以直接照着排查。
4.1 PLC驱动连不上:串口正常但采集值始终为0
现象:设备管理里驱动图标显示正常,串口调试助手也能收到PLC数据,但组态王里所有I/O变量读回来都是0,没有任何报错。
原因:组态王6.5的设备驱动默认“采集频率”是按变化采集,PLC那边如果配置成主动上报,组态王这条链路没主动去读,数据就一直为空。另外USB转串口的虚拟COM口号大于4时,老驱动识别不到。
解决:在设备配置里打开该驱动,把“采集频率”改成200毫秒定时采集,同时把数据变化死区设成0。USB转串口的现场,到设备管理器把COM口号强制改到COM1到COM4之间,改完重启运行系统。这个顺序不要反,先改驱动配置,再改COM口号,两个都改完再测试变量。
4.2 历史趋势图画出来缺一段:历史库文件损坏
现象:历史趋势曲线中间某几天是空白,前后都有数据,画面上的时间轴也是连贯的,看起来就像数据凭空消失。
原因:那几天运行系统被直接断电关过机,组态王6.5的历史数据是先写在缓冲文件里再落盘的,非正常退出会导致缓冲文件没来得及合并。另外磁盘满了也会丢段,但那种一般会报错,不会安静地丢。
解决:备份历史库目录下的.dat文件后,用运行系统的“历史数据修复”功能扫描一遍工程目录,能找回大部分索引。日常防护是把历史数据存储路径指到独立分区,然后每周做一次历史数据导出,别让库文件在C盘攒几个月。
4.3 命令语言不执行:开发系统正常,运行系统里没反应
现象:脚本在开发系统里用“测试”功能试运行一切正常,关掉进入运行系统后,同样的操作画面动了但脚本完全不触发。
原因:最常见的是数据改变脚本绑定的变量在运行系统里没有变化源。比如你在开发系统里手动改变量值测试,脚本当然执行;到了运行系统,这个变量绑定的设备点位没采集数据,值恒定不变,脚本当然不触发。
解决:先把脚本绑定的变量拉到画面上看数值,确认它在运行系统里有变化。再检查“变量未定义”的编译告警,6.5对脚本里引用但数据词典里不存在的变量只给警告不报错,运行系统起来后这个脚本整体失效。处理办法是打开命令语言编辑器,看输出窗口的警告列表,把里面提示的变量名逐个核对,删掉或补定义。
4.4 ASP.NET网页读不到最新数据:接口有值但永远是几分钟前的
现象:Web页面打开时数据是对的,放着不动,页面上的数值永远停在打开那一刻,手动刷新也没用。
原因:两个因素叠加。第一个是ODBC转储本来就是定时插入,不是实时写库,查询频率比插入频率快时,看到的就是旧值;第二个是浏览器对GET请求的缓存,加上有些浏览器对同一URL的响应直接复用,根本不发新请求。
解决:Web端轮询URL加时间戳参数,比如/api/realtime/latest/进水压力?t=1690000000000,同时对API响应设置Cache-Control: no-store。再就是检查组态王侧TimerCnt取模频率,确定入库周期,前端轮询间隔设成入库周期的两倍以上,避免空转。
5. 进阶:变量批量盘点与6.5工程迁移前的验证习惯
工程跑久了,变量几百个,画面几十张,脚本散落各处,靠人工翻一遍很容易漏。我处理这套工程时的习惯是用脚本把变量导出文件做一次批量盘点,既核对引用合法性,也给以后迁移到7.x留一份底账。
组态王6.5的数据词典支持把变量清单导出成Excel或CSV文件,导出后用Python做检查:
import csv rows = list(csv.DictReader(open("export.csv", encoding="gbk"))) # 统计变量类型分布 type_count = {} unbound_io = [] for r in rows: vt = r["变量类型"].strip() type_count[vt] = type_count.get(vt, 0) + 1 if vt.startswith("I/O") and not r.get("连接设备", "").strip(): unbound_io.append(r["变量名称"]) print("变量总数:", len(rows)) print("类型分布:", type_count) print("未绑定设备的I/O变量:", unbound_io)老版本导出文件默认GBK编码,用encoding="gbk"打开,否则中文变量名全变乱码。这份清单的价值在迁移:把6.5工程升级到7.x前,先比对变量数量、类型、寄存器地址,升完再拿清单逐项核对,比在界面里一个个翻可靠得多。迁移时几个硬性检查项是:所有驱动换成对应新版本、脚本里6.5特有的字符串处理函数按新语法重写、画面里的老控件替换成新控件库里的等价物。
从那以后我每次动完脚本都强制走一遍这个流程:先导出变量清单查引用,再在开发系统里编译看警告,最后进运行系统观察半小时数据。这套工程拿到手,变量表、画面、脚本、SQL配置四个文件组逐层过一遍,确认关系闭环了再上线。希望帮到你。
本文还有配套的精品资源,点击获取