
简介这是一份面向工业自动化开发者的力控实时数据库接口开发SDK对应pspace 6.0版本。基于C# .NET Framework 4.0环境可快速实现实时/历史数据读写、点表管理、报警事件订阅等核心功能适用于生产监控、能源管理等系统的二次集成。压缩包共49个文件大小6.76MB包含12个DLL动态库、12个头文件、10个C示例源码、2个LIB导入库以及PDB调试符号同时提供Win32和X64两种平台版本。目前已有235人学习下载。SDK附有完整API头文件、示例工程与调试符号开发者可通过源码和库文件深入理解接口调用方式有效缩短项目开发周期。无论是构建数据分析报表还是编写自定义控制逻辑这套SDK都能提供从底层访问到上层应用的完整支撑。1. pspace 实时数据库 SDK 是什么那份 rar 到底能解决什么接到 SCADA 数据接入需求时最难受的不是接口不会调而是不确定要不要自己写整套协议转换。psAPISDK 6.0.1.9 是力控 pspace 实时数据库的二次开发接口包里面是 C 接口的动态库、头文件和示例工程覆盖快照读写、历史采样和点位订阅名字里的_2一般是修订补丁包建议直接覆盖安装到旧 SDK 上。对做上位机集成、MES 取数、历史曲线补采的人来说这份 SDK 能省掉最底层那层封装拿到手先花十分钟确认三件事——能连、能读、能取历史基本就等于把整条数据通道的前半段打通了。2. 解析 psAPISDK6.0.1.9四层目录、编译准备与第一遍跑通解压 rar 后我习惯先不动代码而是把 doc 目录通读一遍看清库文件放哪里、头文件依赖什么再决定用哪种方式编译。很多翻车现场不是接口不会调而是头文件路径没加、运行库没拷、链接库选错了位数白白浪费半天。这一章先把目录结构和编译链路理清楚。2.1 解压后的四层目录include、lib、doc、samples 各管哪一段SDK 这类资源通常按四个目录组织psAPISDK 6.0.1.9 也不例外。include 里是psapi.h、pstypes.h这类头文件定义了句柄类型、结构体和对外函数声明lib 目录下一般拆成 x64 与 x86 两个子目录里面是编译时链接用的.lib导入库bin 目录放运行时需要的psapi.dll如果是 Linux 版则为libpsapi.sodoc 目录是接口说明文档samples 目录则是官方示例工程。目录典型内容什么时候用includepsapi.h、pstypes.h写代码时 includelib/x64psapi.lib链接 64 位程序lib/x86psapi.lib链接 32 位程序binpsapi.dll / libpsapi.so程序运行时加载docAPI 接口说明、版本变更记录查函数签名与返回码samples示例工程读点、写点、历史查询抄作业起点我一般把bin目录里的 dll 直接丢到编译产物的同级目录而不是装到系统PATH这样换版本时不会污染机器上其他工程。注意lib目录如果同时有 debug 和 release 子目录链接时务必选对混用会出现运行时诡异崩溃后面避坑章细说。2.2 编译准备三步走头文件路径、库路径与运行时依赖拿到 SDK 先做最小编译验证别急着写业务代码。第一步确认编译器位数64 位程序必须配 x64 的 lib32 位程序配 x86。第二步设置库路径第三部把 dll 放到可执行文件旁边。# 以 Windows 命令行编译为例先设 SDK 根目录变量 set SDK_ROOTD:\psAPISDK6.0.1.9_2 # 用 VS 的 cl.exe 编译一个最小测试程序 cl /nologo /I%SDK_ROOT%\include /EHsc test_read.c ^ /link /LIBPATH:%SDK_ROOT%\lib\x64 psapi.lib这段命令做的事情是/I告诉编译器去 SDK 的 include 目录找psapi.h/LIBPATH告诉链接器去 x64 的 lib 目录找psapi.lib编译产物test_read.exe生成后把bin里的psapi.dll复制到 exe 同一目录。这里最容易被忽略的是/EHsc如果工程里用了 C 的异常处理却没有开启该选项回调函数里抛异常会导致程序直接退出在 SDK 场景里尤其明显。Linux 下等价命令是gcc test_read.c -I$SDK_ROOT/include -L$SDK_ROOT/lib/x64 -lpsapi -o test_read同时把LD_LIBRARY_PATH指到bin目录或者用rpath写死运行库路径。日常习惯是先在命令行把上述三步跑通再用 CMake 或 VS 工程把路径固化下来后面接业务代码就顺了。2.3 跑通自带示例从连接串到第一条测点数据samples 里一般有一个demo_read.c类似的文件核心逻辑是连接实时库、读一个测点的快照值、打印出来。我建议先把它改成只读本机回环地址端口保持默认 7100账号用管理工具里建好的用户。#include stdio.h #include psapi.h int main(void) { /* 建立到本机 pspace 实时库的连接 */ ps_handle_t h ps_connect(127.0.0.1, 7100, admin, pspace); if (h NULL) { printf(connect failed\n); return -1; } /* 读取实时库里已建好的测点快照值 */ double value 0.0; int ret ps_read_value(h, 锅炉.主汽温度, value, 0); if (ret 0) { printf(value: %.2f\n, value); } else { printf(read failed, ret%d\n, ret); } ps_disconnect(h); return 0; }这段代码先调用ps_connect建立连接四个参数分别是目标地址、端口、用户名和密码ps_read_value接收连接句柄、测点全名、数值指针和时间戳指针最后一个 0 表示取最新快照不关心历史时间戳。测点全名的点号分隔是 pspace 的命名习惯区域在点前面比如锅炉.主汽温度如果点不存在返回码非 0不会导致程序崩溃。第一次跑通后建议顺手用ps_ping(h)或对应接口探一下连接健康状态这个动作在后面排查超时问题时很有用。示例工程能打印出数值说明头文件、链接库、运行时依赖、服务地址四件事全部正确接下来就可以进入 API 走读阶段。3. 核心 C 接口走读连接句柄、快照读写与历史聚合查询实时数据库的接口设计和关系库不太一样关系库讲究 SQL实时库更讲究点位和时间。pspace 的 SDK 核心就三块连接管理、快照读写、历史查询把这三块用熟五成以上的取数需求都能覆盖。3.1 连接管理ps_connect 的返回、超时与连接串写法ps_connect是最常用的入口返回一个ps_handle_t句柄后续所有调用都要带着它。连接失败时返回空指针但失败原因不止一种服务没启动、端口不通、账号密码错、license 过期都可能返回空。光看空指针不够配合日志或错误码接口才能定位我一般会先做一次本机连通性验证。/* 使用带超时配置的连接接口 */ ps_config_t cfg; ps_cfg_init(cfg); cfg.timeout_ms 3000; /* 3 秒超时避免卡死 */ cfg.keepalive_sec 60; /* 60 秒心跳 */ ps_handle_t h ps_connect_ex(cfg, 192.168.1.10, 7100, admin, pspace);ps_connect_ex比普通ps_connect多了一个配置结构体适合在程序里手动控制连接行为。timeout_ms设为 3000 是经验值内网环境下 1 秒内通常能连上设太短容易误判设太长首包失败会卡住整个界面线程。keepalive_sec表示客户端和服务端的空闲心跳间隔如果程序要长时间挂机订阅点位这个参数建议不小于 30否则防火墙上空闲连接可能被切断回调就再也收不到数据了。要注意整个 SDK 的句柄不是线程安全的多线程场景下必须为每个线程创建独立连接或者用锁保护同一个句柄。很多刚接触实时库的开发者下意识用一个全局句柄到处传遇到偶发崩溃先查这里。3.2 快照读写读点、写点、时间戳对齐快照值就是某个测点在当前时刻的最新数值读起来简单写起来有一点要特别注意写数据时要带上时间戳这个时间戳决定了数据在实时库归档时的位置。/* 读取单个测点的快照值 */ ps_time_t ts; double temp 0.0; int ret ps_read_value(h, 锅炉.主汽温度, temp, ts); printf(temp%.2f at %s\n, temp, ps_format_time(ts)); /* 写入一个测点的快照值 */ ps_time_t now; ps_get_now(now); double setpoint 45.5; ret ps_write_value(h, 锅炉.给水流量, setpoint, now);ps_read_value第四个参数ts是输出参数函数返回后里面是该测点的最后更新时间打印出来可以判断这个点是否一直在刷新。如果时间戳停在很久以前说明源头设备已经断线读到的是缓存旧值这种数据在报表里直接使用会出问题。ps_write_value的时间戳由调用方传入ps_get_now获取的是本机系统时间这是最简单也是最容易踩坑的方式。本机时间和 pspace 服务端时间如果存在秒级偏差写入实时库后按时间轴查询数据时会看到对不齐的曲线。正规做法是用ps_get_server_time()这类接口取服务端时间再赋值保证时间戳来自数据源头。3.3 历史查询按时间段拉数而不是按条数拉数历史查询是实时库最常用的功能。SQL 习惯是select * from table limit 100实时库则换了一套逻辑给定起始时间和结束时间再给定重采样间隔库端直接吐出这段时间内每个周期的数据。/* 构造历史查询参数 */ ps_history_query_t q; ps_history_init(q); q.start_time ps_make_time(2024-01-01 00:00:00); q.end_time ps_make_time(2024-01-01 00:10:00); q.interval_ms 1000; /* 按 1 秒间隔重采样 */ q.aggregate PS_AGG_AVG; /* 聚合方式平均 */ static ps_history_data_t *rows NULL; int count ps_query_history(h, 锅炉.主汽温度, q, rows); for (int i 0; i count; i) { printf(%s %.2f\n, ps_format_time(rows[i].ts), rows[i].value); } ps_free_history(rows, count);ps_history_query_t里的interval_ms决定重采样粒度设为 1000 表示每秒一个点库端会把原始数据按照聚合方式压成这个间隔。aggregate支持平均值、最大值、最小值等选 AVG 适合看趋势选 MAX/MIN 适合看峰值报警。如果你只需要原始记录把interval_ms设为 0 表示不过滤直接吐全部历史记录但数据量大的时候要小心内存占用。ps_free_history是必须调用的释放函数忘记调用最直接的结果是内存增长程序跑一天可能吃掉几个 GB。这里再提醒一句历史查询接口返回的rows指针是一次性的内部可能只分配了一块连续内存按行处理后及时释放不要尝试自己 free 结构体里的字段。4. 避坑清单连接超时、编码错乱、归档空档与句柄泄漏SDK 用起来顺不顺一半看环境一半看细节。这四条踩坑记录都是从实际项目里沉淀下来的新手遇到可以少走很多弯路。4.1 连接超时外部程序连不上本机工具却能连上现象用管理工具在服务器本机能正常读写但部署到另一台机器上的程序调用ps_connect总是超时返回空句柄。原因大概率是 Windows 防火墙拦截了 7100 端口的入站请求。管理工具跑在服务端本机走的是回环接口不受防火墙规则限制客户端程序从另一台机器访问SYN 包直接被打掉。解决在服务端防火墙放行 TCP 7100 端口或者放行 psapi.dll 对应的进程名。验证方法是在客户端机器执行telnet 服务器IP 7100能进入空界面就代表端口通了。遇到端口不通还要顺手排查一下服务端监听地址是否绑定了 127.0.0.1如果是要改成 0.0.0.0。4.2 测点名中文乱码读出来的点位名是花的现象程序读点位列表或测点名时中文显示成乱码但服务端管理工具里正常。原因pspace 的 SDK 在 Windows 版默认使用 ANSIGBK编码返回字符串而程序如果是按 UTF-8 方式解析字符数组中文直接错位。Linux 版通常默认 UTF-8跨平台传递时容易出现两端编码不一致。解决接到字符串后先判断当前平台Windows 下用MultiByteToWideChar转成 UTF-16或调用 SDK 提供的ps_set_encoding接口明确切换编码模式。如果 SDK 没有提供该接口就统一在工程代码里做一次 GBK 到 UTF-8 的转码别指望两端默认一致。4.3 历史查询出现空档中间缺了一段时间的数据现象查询某测点 10 分钟历史数据结果只有前 5 分钟和后 3 分钟中间 2 分钟一个点都没有日志里也没报错。原因一是实时库的归档策略问题采集中断、设备停机、写入进程崩溃都可能导致数据没落库二是interval_ms设置得太小原始数据本身就存在采样空洞重采样后空洞被放大。解决先检查原始存储表是否有数据把interval_ms设为 0 拉一次完整原始记录确认空洞在源头还是查询层。如果是源头断档需要检查采集程序是否还活着如果是查询层问题可以把聚合方式改成PS_AGG_LAST这样每个时段内取最后一个有效值能缓解一部分空洞表现。4.4 句柄泄漏长时间运行内存只涨不跌现象程序连续运行 48 小时后内存占用翻倍最终触发内存不足警报。原因代码里每次循环都调用ps_connect和ps_disconnect或者每查询一次历史就 new 一堆结构体却没有调用ps_free_history。实时库客户端在每次连接建立时都会分配一组上下文不关闭就一直在。解决连接句柄要长生命周期复用不要在循环里反复建立历史查询结果用完后立刻释放有条件的话在退出前调用 SDK 提供的ps_shutdown做资源回收。把ps_free_history写进查询函数的统一出口处比在每一个调用点手写释放可靠得多。5. 进阶用法订阅回调、批量读写与 100ms 采样节奏的调整轮询模式在点位少、频率低几秒一次的场景够用但点位多了以后轮询一方面浪费带宽另一方面数据变化响应不及时。psAPISDK 6.0.1.9 支持订阅机制服务端数据变化时主动推送配合批量读写可以把采样节奏从 1 秒压到 100 毫秒级别。5.1 订阅回调数据变化才通知省掉无效请求订阅的基本思路是客户端先声明自己关心哪些点位之后当这些点位发生数值变化时服务端主动调用客户端注册的回调函数不需要客户端反复去问。/* 订阅回调函数在数据变化时被 SDK 调用 */ void on_point_change(ps_point_t *p, void *context) { /* p 里包含点名和最新值这里只做打印 */ printf(point %s %.2f\n, p-name, p-value); } /* 订阅指定区域所有测点通配符 * 支持按前缀匹配 */ int ret ps_subscribe(h, 锅炉.*, on_point_change, NULL);回调函数里建议只做轻量处理比如把数值塞进内存队列再由另一个工作线程批量写库或触发业务逻辑。原因很现实回调是 SDK 内部线程触发的如果你在回调里做了耗时操作比如打日志到磁盘、调用远程接口会拖慢 SDK 的派发线程导致后续数据推送延迟甚至堆积。订阅用的通配符语法要注意锅炉.*匹配的是锅炉区域下所有测点但通常不递归匹配子区域如果要订阅锅炉下所有子区域需要写成锅炉.*.*或者用 SDK 文档里约定的递归通配符。点位量大的时候建议按区域分批订阅而不是一条通配符订阅全库。5.2 批量读写一次调用处理多个测点吞吐量直接翻倍逐点读写适合调试生产环境里上百个测点逐个调用每次都有一次网络往返性能损耗很明显。批量接口就是为这个场景准备的一次调用传一个结构体数组SDK 内部循环处理。static ps_point_t pts[2]; strcpy(pts[0].name, 锅炉.给水流量); pts[0].value 12.5; strcpy(pts[1].name, 锅炉.炉膛负压); pts[1].value -20.1; /* 一次性写入两个测点返回实际写入个数 */ int written ps_write_values(h, pts, 2, 0); printf(written %d points\n, written);批量读和批量写是对称的ps_read_values接收一个点位名数组函数返回后每个结构体里填好对应数值和时间戳。批量接口内部减少了一次次调用的上下文切换在点位数量超过 50 时效果最明显。一个实测经验100 个点逐点读耗时约 200ms批量读一次调用约 30ms吞吐量提升接近 7 倍代价是内存里需要一次性准备好整个包的缓冲区点位过多时要分批处理。批量写最后一个参数flags可以用来控制事务语义0 表示逐点独立写某些点失败不影响其他点如果传了事务标志则一批点要么全部生效要么全部回滚具体取值查 SDK 头文件里的宏定义。现场写测点要谨慎批量写容易覆盖操作员正在调整的实时值最好先在上位机上做好确认逻辑。5.3 从 1s 到 100ms回调缓冲与线程安全订阅机制配合批量读写可以把点位数据刷新节奏从秒级推到 100ms 量级。比如现场有 50 个温度测点订阅后数据变化立刻回调每个回调里把数据暂存到队列业务线程每 100ms 批量取一次队列数据这样既不丢点也不阻塞回调线程。/* 业务线程每 100ms 批量取队列数据 */ while (run) { msleep(100); pthread_mutex_lock(mu); int n queue_drain(q, buffer, MAX_BATCH); pthread_mutex_unlock(mu); if (n 0) { ps_write_values(h, buffer, n, 0); } }这个模式的关键是队列的线程安全性queue_drain需要把队列里的点一次性搬走并清空防止读取和写入同时发生导致数据错乱。我一般用互斥锁包住取出和清空两个动作而不是先取出再单独清空否则两个线程同时操作会丢数据。100ms 采样节奏下单条网络连接的往返延迟如果在 10ms 以内订阅推送的实时性足够满足大部分工艺监控需求。如果延迟高于 50ms先检查服务端和客户端是否跨网段再看防火墙是否有流量整形规则最后才考虑调大 keepalive 参数。库端的采样周期本身也需要相应调整到 100ms否则订阅方收得再快库端落盘的数据点还是稀的。6. 时间戳校验法用一条命令验证链路的数据完整性与延迟接好 SDK 后先别急着写业务界面我在每个新项目里都会先做一版时间戳校验工具专门检查实时链路的数据完整性和延迟表现。工具逻辑不复杂订阅一个高频测点记录每个回调触发时的本地时间t_now与点位自带时间戳t_point做差差值超过阈值就打印出来。实际实现时写一个命令行小工具启动参数传入测点名称和期望间隔运行 10 分钟统计回调次数、延迟均值、最大延迟和数据丢点率。延迟均值能反映链路整体通畅程度最大延迟暴露偶发卡顿丢点率则是数据完整性的硬指标。历史查询那边再做一个互补校验查询同一测点最近一小时数据检查相邻行时间戳增量是否等于设定周期出现间隙打印缺失时间段。时间差的计算要小心两个时间戳的时区基准。SDK 返回的时间戳如果带时区偏移直接用减法会引入几小时的恒定误差我一般先把两方都转成 UTC 毫秒数再相减。如果延迟曲线整体高出一个固定值优先怀疑有没有经过一层中间转发正常的直连场景延迟波动应该在几十毫秒以内。从第一次拿到 psAPISDK 6.0.1.9 到现在我每次接入新的实时库环境都强制先跑一遍这个校验再决定怎么设置interval_ms、订阅粒度、批量大小这些参数。数据完整性和延迟的测量结果比任何文档都更能反映现场真实链路质量也是后面排障时最重要的参照系。希望这套三层走读法——先跑通示例、再走读核心接口、最后验链路质量——能帮你省下第一周摸索的时间。本文还有配套的精品资源点击获取