简介:这份资源面向工业自动化工程师与WinCC初学者,聚焦西门子WinCC监控系统的报警、变量数据读取与归档操作,帮助解决从WinCC工程数据库中提取历史数据、分析报警事件的实际问题。压缩包共41个文件,约84KB,以C#源码(17个cs文件)为核心,配合5个resx与4个resources资源文件、3个可执行程序及config配置、csproj工程文件,构成一套可直接编译运行的WinCC数据读取示例工程。目前已有382人学习下载。工程围绕报警日志、变量记录与用户归档三大模块展开,涵盖数据库连接、SQL查询构建、结果集处理与数据导出等环节,读者可据此理解WinCC归档数据的访问机制,掌握通过编程接口读取报警与变量历史值的方法,并借鉴其窗体与工具类的组织方式,快速搭建自己的数据采集与分析程序。
1. 从一份 ReadWinCCData_1.rar 说起:WinCC 归档数据到底怎么落到数据库里
现场调试过 WinCC 的人多半遇到过这个场景:画面上的趋势曲线跑得好好的,可一旦要拿历史数据做报表、做能耗分析、或者对接 MES,就发现归档数据像锁在黑匣子里——看得见,导不出。这份 ReadWinCCData_1.rar 就是冲着这个痛点来的,它是一套读取 WinCC 归档数据并写入数据库的工程源码,核心解决的是「WinCC 归档数据怎么被外部程序稳定读出来、再落到关系型数据库」这件事。适合两类人:一是做 SCADA 上位机、需要把 WinCC 历史数据二次利用的自动化工程师;二是接手了别人 WinCC 工程、要补数据接口的运维和集成人员。它不解决画面组态,只解决数据出口。
2. 先搞懂 WinCC 归档数据的存储结构:为什么不能直接读数据库
2.1 归档不是一张表,而是分段压缩的时序块
很多人第一反应是:WinCC 归档数据不就在 SQL Server 里吗,直接连库查表不就行了。真去翻过的人都知道,WinCC 的归档(Tag Logging)在 SQL Server 里落成的是一堆以Archive开头的分段表,加上TLG_F之类的元数据表,字段是二进制压缩过的,时间戳和值不是一一对应的行。你直接SELECT * FROM出来的东西,人眼根本读不懂。这就是为什么必须走 WinCC 提供的接口,而不是硬啃数据库。
常见做法是两条路:一条是 WinCC 自带的连通性包(Connectivity Pack),通过 OLE DB 或 WinCC OLE DB Provider 去查归档;另一条是走 WinCC 的脚本或 ODK(Open Development Kit)。这份工程走的是前者,用 OLE DB 把归档当数据源查,查出来的结果再写进目标数据库。选它的理由是:不用停 WinCC 运行、不用改归档组态、对历史数据是只读的,风险最低。
2.2 连通性包和 OLE DB Provider 的对应关系
要跑通这套东西,机器上得先有 WinCC Connectivity Pack,它装完会注册一个WinCCOLEDBProvider。连接字符串里指定归档所在的服务器和归档名,查询用 WinCC 自己的 SQL 方言(基于 ANSI SQL 扩展)。这里有个容易翻车的点:Provider 的版本必须和 WinCC 版本对齐,WinCC 7.4 配的 Provider 拿去连 7.5 的归档,十有八九报「找不到归档」。所以第一步不是写代码,是确认版本。
| 组件 | 作用 | 版本对齐要求 |
|---|---|---|
| WinCC Connectivity Pack | 提供归档访问接口 | 与 WinCC 主版本一致 |
| WinCC OLE DB Provider | 实际执行归档查询 | 随 Connectivity Pack 安装 |
| 目标数据库 | 存读取结果 | SQL Server / MySQL 均可 |
| 归档服务器 | 数据来源 | 需开放对应访问权限 |
2.3 读取流程拆成四步
整个链路我一般拆成四步走,这样出问题好定位:第一步,确认归档名和变量名,在 WinCC 里用变量记录编辑器能看到;第二步,拼连接字符串,连上 Provider;第三步,写查询语句,按时间范围拉数据;第四步,把结果集映射到目标库的表结构里写入。这四步任何一步错,现象都不一样,后面避坑章节会细说。
3. 把归档读出来:连接字符串、查询语句与结果映射
3.1 连接字符串怎么拼
连接字符串是这套工程的第一道门槛,拼错了连报错都看不懂。典型写法如下,注意Catalog指向的是归档所在的数据库实例,Data Source是 WinCC 服务器名:
// WinCC OLE DB Provider 连接字符串示例 string connStr = "Provider=WinCCOLEDBProvider.1;" + // Provider 名称,版本随 WinCC 变 "Catalog=CC_MyProject_20240101_000000R;" + // 归档运行库名,在 WinCC 里查 "Data Source=WINCC-SERVER;" + // WinCC 服务器计算机名 "Initial Catalog=CC_MyProject;" + // 项目归档库 "Integrated Security=SSPI;"; // 用 Windows 集成认证,别用明文账号逻辑说明:Provider决定用哪个驱动,写错直接抛「未注册的提供程序」;Catalog是归档运行库名,格式是CC_项目名_日期_序号R,这个值每个项目都不一样,必须去 WinCC 的归档组态里核对;Integrated Security=SSPI表示用当前 Windows 账号认证,比在字符串里写账号密码安全,也少一个出错点。参数上唯一能改的是Data Source,换成你实际的服务器名或 IP。
3.2 查询语句的写法与时间范围
WinCC 的查询语法和标准 SQL 有差异,时间字段要用它自己的函数处理。下面这条是拉某个变量在指定时间段内的归档值:
-- 查询指定变量在时间范围内的归档值 SELECT ValueID, -- 变量在归档中的内部编号 TimeStamp, -- 归档时间戳 RealValue, -- 实际工程值 QualityCode -- 质量码,判断数据是否有效 FROM Archive WHERE TimeStamp >= '2024-01-01 00:00:00' AND TimeStamp <= '2024-01-01 08:00:00' AND ValueID = 12 -- 对应具体变量,需先在变量表里查到 ORDER BY TimeStamp ASC;逻辑说明:ValueID不是变量名,是归档内部编号,得先在 WinCC 变量记录里查到变量对应的 ID,写错就查不到数据还不报错,这是最阴的坑;QualityCode一定要带上,质量码非 0 的数据在后续分析里要过滤掉,否则报表会出现莫名其妙的跳变;时间范围建议按班次或小时切,一次拉太大会让 Provider 超时。
3.3 结果集写入目标数据库
读出来之后要落到目标库,常见做法是建一张结构对齐的表,然后批量插入。下面用参数化插入避免拼接 SQL:
// 把归档结果批量写入目标数据库 using (var target = new SqlConnection(targetConnStr)) { target.Open(); using (var cmd = new SqlCommand( "INSERT INTO TagHistory (TagId, SampleTime, Value, Quality) " + "VALUES (@id, @time, @val, @qc)", target)) { cmd.Parameters.Add("@id", SqlDbType.Int); cmd.Parameters.Add("@time", SqlDbType.DateTime); cmd.Parameters.Add("@val", SqlDbType.Float); cmd.Parameters.Add("@qc", SqlDbType.Int); foreach (var row in archiveRows) // archiveRows 是上一步读出的集合 { cmd.Parameters["@id"].Value = row.ValueID; cmd.Parameters["@time"].Value = row.TimeStamp; cmd.Parameters["@val"].Value = row.RealValue; cmd.Parameters["@qc"].Value = row.QualityCode; cmd.ExecuteNonQuery(); } } }逻辑说明:用参数化而不是字符串拼接,一是防注入,二是时间字段的格式转换交给驱动处理,省得自己格式化踩时区的坑;批量插入时如果数据量大,建议改成SqlBulkCopy,逐条ExecuteNonQuery在几万条以上会明显变慢。参数上@qc存质量码,后续查询时用WHERE Quality = 0过滤无效点。
4. 避坑与排查:归档读取最常见的五类翻车
4.1 现象:连接报「未找到提供程序」
原因:机器上没装 Connectivity Pack,或者装了但版本和 WinCC 对不上,Provider 没注册成功。解决:先在「ODBC 数据源管理器」的「提供程序」页里确认WinCCOLEDBProvider.1在不在,不在就重装对应版本的 Connectivity Pack,装完重启一次服务。
4.2 现象:查询返回空结果,但归档里明明有数据
原因:ValueID写错了,或者Catalog指向了错误的归档运行库。解决:去 WinCC 变量记录里逐个核对变量对应的 ValueID,别凭记忆写;Catalog用归档组态里显示的那个完整名字,注意结尾的R不能漏。
4.3 现象:数据能读出来,但时间戳整体偏移几小时
原因:WinCC 服务器和读取程序的时区设置不一致,或者归档本身存的是 UTC。解决:统一两边时区,读取后在程序里做一次显式转换,别指望驱动自动处理;转换逻辑写死在一个函数里,别散落在各处。
4.4 现象:大批量读取时程序卡死或超时
原因:一次查询的时间跨度太大,Provider 在服务端做压缩解压,数据量一上来就顶不住。解决:按小时或按班次分片查询,每片读完就写库释放内存;如果还慢,把查询放到独立线程,别阻塞主界面。
4.5 现象:写入目标库时偶发主键冲突
原因:重复读取了同一时间段,或者程序重启后没记录上次读到的位置。解决:在目标库上对(TagId, SampleTime)建唯一索引,插入用MERGE或先查后插;更稳的做法是维护一张断点表,记录每个变量最后读取的时间戳,重启后从断点续读。
提示:调试阶段先把时间范围缩到 10 分钟,确认链路通了再放大,别一上来就拉一个月的数据,出问题根本没法定位。
5. 进阶:把归档读取做成可复用的增量同步
5.1 用断点表实现增量读取
一次性全量读取只适合首次初始化,日常运行必须做增量。思路很简单:为每个变量维护一条断点记录,每次只读「上次时间戳到现在」这一段。下面这张表就是断点表的结构:
| 字段 | 类型 | 说明 |
|---|---|---|
| TagId | int | 变量内部编号 |
| LastTime | datetime | 上次成功读取到的时间戳 |
| UpdateTime | datetime | 断点更新时间 |
| Status | int | 0 正常,1 异常待重读 |
每次读取前先查LastTime,查询语句的起始时间就用它,读完写库成功后再更新断点。这样即使程序中途挂了,重启后也能从断点继续,不会漏数据也不会重复。
5.2 异常重读与质量码过滤
归档读取偶尔会因为网络或服务重启失败,这时候别直接跳过,把Status置 1,下一轮优先重读这段。质量码过滤放在写库前做,QualityCode != 0的点标记出来但不丢弃,单独存一张异常表,方便后面排查是采集问题还是通信问题。我一般会在断点表上加一个重试次数字段,超过三次就告警,避免死循环。
5.3 一个我踩过的坑:别在 WinCC 服务器上跑读取程序
早期图省事,把读取程序直接部署在 WinCC 服务器上,结果程序一占资源,画面刷新都变卡,被现场投诉。后来改成在独立的采集机上跑,通过局域网连 Provider,WinCC 服务器只负责归档,压力小了很多。从那以后我每次部署这类程序,都强制先确认它和 WinCC 服务器是不是同一台机器,是的话一律拆开。读取频率也别设太密,归档本身有采集周期,读得比采集还快纯属浪费。
5.4 验证读取是否正确的笨办法
别信程序日志,信数据。拿一个你知道变化规律的变量,比如每小时整点跳一次的产量计数,读出来跟画面对一遍,时间戳和值都对得上,才算链路通了。再挑一段有停机的时间,看质量码是不是按预期变化。这套笨办法我每次都走一遍,比看任何日志都靠谱。希望帮到你。
本文还有配套的精品资源,点击获取