文章目录
-
- [1 开源库概要](#1 开源库概要)
- [2 API介绍](#2 API介绍)
-
- [2.1 Tag Model(标签模型)](#2.1 Tag Model(标签模型))
- [2.2 Status Codes(状态码)](#2.2 Status Codes(状态码))
- [2.3 Versions and Checking Library Compatibility(版本和检查库的兼容性)](#2.3 Versions and Checking Library Compatibility(版本和检查库的兼容性))
- [2.4 Tag Life Cycle(标签生命周期)](#2.4 Tag Life Cycle(标签生命周期))
- [2.5 Callbacks(回调)](#2.5 Callbacks(回调))
-
- [2.5.1 Event Callback(事件回调)](#2.5.1 Event Callback(事件回调))
- [2.5.2 Log Callback(日志回调)](#2.5.2 Log Callback(日志回调))
- [2.6 Aborting an Operation(中止操作)](#2.6 Aborting an Operation(中止操作))
- [2.7 Reading a Tag(读标签)](#2.7 Reading a Tag(读标签))
- [2.8 Retrieving Tag Status(获取标签状态)](#2.8 Retrieving Tag Status(获取标签状态))
- [2.9 Writing a Tag(写标签)](#2.9 Writing a Tag(写标签))
- [3.0 Retrieving/Setting Tag Size(获取/设置标签大小)](#3.0 Retrieving/Setting Tag Size(获取/设置标签大小))
-
- [3.0.1 获取标签大小](#3.0.1 获取标签大小)
- [3.0.2 设置标签大小](#3.0.2 设置标签大小)
- [3.1 Getting Tag Data Elements(获取标签数据元素)](#3.1 Getting Tag Data Elements(获取标签数据元素))
- [3.2 Tag Internal Attribute Functions(标签内部属性函数)](#3.2 Tag Internal Attribute Functions(标签内部属性函数))
- [3.3 Debugging(调试)](#3.3 Debugging(调试))
1 开源库概要
- libplctag是一个开源的用于与PLC(可编程逻辑控制器)通信的库,它提供了一组函数和工具,可以方便地读写PLC数据。libplctag是用C语言编写的,广泛用于工业自动化、机器人控制等领域。
- 以下是libplctag库的一些特别:
- 多种通信协议支持:libplctag可以与多种常用的PLC通信协议进行通信,包括Modbus TCP、Ethernet/IP等;
- 简单易用的API:libplctag提供了简洁的API,使得开发人员可以轻松地读取和写入PLC的数据。库中封装了一系列函数,用于连接PLC、读取标签值、写入标签值等操作;
- 跨平台支持:libplctag可以在多个操作系统上运行,包括Linux、Windows、Mac等。这使得开发人员可以在不同的环境中使用相同的库进行PLC通信开发。
- 开源和社区支持:libplctag是一个开源项目,代码托管在GitHub上,可以自由获取和修改。同时也有活跃的社区支持,开发人员可以参与讨论、报告问题和贡献代码。
- 开源库的GitHub网址:
2 API介绍
此部分本人会简要介绍一下常用API,用于个人记录。更详细的说明请自行移步2.5.0 API文档网址:
https://github.com/libplctag/libplctag/wiki/API#tag-life-cycle
2.1 Tag Model(标签模型)
- 在介绍API之前,需要对模型进行简要说明。开源库将每个标签或标签中的字段作为一个单独的句柄,供程序使用。没有PLC的公开概念。只有标签。
- 标签是对PLC内存区域的本地引用。根据PLC类型和协议的不同,该区域可能会被命名。对于某些协议,区域只是一个类型和寄存器编号(如Modbus)。对于其他协议,则是一个名称、可能的数组元素、字段名称等(如基于CIP的PLC)。
- 你的程序直接控制句柄的生存期。可以通过
plc_tag_create()
打开PLC标签的句柄,通过plc_tag_destroy()
释放与函数一起使用的资源。
2.2 Status Codes(状态码)
- 大多数函数返回状态码,详情如下(括号内为对应数值):
- PLCTAG_STATUS_PENDING (1) - 操作正在进行中。不是错误。
- PLCTAG_STATUS_OK (0) - 无错误。操作成功或标签状态良好。
- PLCTAG_ERR_ABORT (-1) - 操作已中止。
- PLCTAG_ERR_BAD_CONFIG (-2) - 由于配置不正确,操作失败。通常从远程系统返回。
- PLCTAG_ERR_BAD_CONNECTION (-3) - 由于某种原因,连接失败。例如,这可能意味着远程 PLC 已重新通电。
- PLCTAG_ERR_BAD_DATA (-4) - 从远程 PLC 接收的数据无法破译或无法处理。也可以从无法处理发送给它的数据的远程系统返回。
- PLCTAG_ERR_BAD_DEVICE (-5) - 当寻址的内容不存在时,通常从远程系统返回。
- PLCTAG_ERR_BAD_GATEWAY (-6) - 通常在库无法连接到远程系统时返回。
- PLCTAG_ERR_BAD_PARAM (-7) - 当标签创建属性字符串不正确时返回的常见错误。
- PLCTAG_ERR_BAD_REPLY (-8) - 通常在远程系统返回意外响应时返回。
- PLCTAG_ERR_BAD_STATUS (-9) - 通常由远程系统在状态不佳时返回。
- PLCTAG_ERR_CLOSE (-10) - 尝试关闭某些资源时出错。
- PLCTAG_ERR_CREATE (-11) - 尝试创建某些内部资源时出错。
- PLCTAG_ERR_DUPLICATE (-12) - 当某些内容(即重复的连接 ID)被错误复制时,远程系统返回的错误。
- PLCTAG_ERR_ENCODE (-13) - 尝试对某些数据(如标签名称)进行编码时返回错误。
- PLCTAG_ERR_MUTEX_DESTROY (-14) - 内部库错误。看到这种情况是非常不寻常的。
- PLCTAG_ERR_MUTEX_INIT (-15) - 同上。
- PLCTAG_ERR_MUTEX_LOCK (-16) - 同上。
- PLCTAG_ERR_MUTEX_UNLOCK (-17) - 同上。
- PLCTAG_ERR_NOT_ALLOWED (-18) - 当不允许操作时,通常从远程系统返回。
- PLCTAG_ERR_NOT_FOUND (-19) - 通常在找不到某些内容时从远程系统返回。
- PLCTAG_ERR_NOT_IMPLEMENTED (-20) - 未实现有效操作时返回。
- PLCTAG_ERR_NO_DATA (-21) - 当预期数据不存在时返回。
- PLCTAG_ERR_NO_MATCH (-22) - 类似于NOT_FOUND。
- PLCTAG_ERR_NO_MEM (-23) - 内存分配失败时由库返回。
- PLCTAG_ERR_NO_RESOURCES (-24) - 当某些资源分配失败时,由远程系统返回。
- PLCTAG_ERR_NULL_PTR (-25) - 通常是内部错误,但在 API 调用中使用无效句柄时可能会返回。
- PLCTAG_ERR_OPEN (-26) - 打开套接字等资源时出错时返回。
- PLCTAG_ERR_OUT_OF_BOUNDS (-27) - 通常在尝试将值写入标签数据边界之外的标签或读取标签数据边界之外的值时返回。
- PLCTAG_ERR_READ (-28) - 在读取操作期间发生错误时返回。通常与套接字问题有关。
- PLCTAG_ERR_REMOTE_ERR (-29) - 未指定或无法转换的远程错误会导致此问题。
- PLCTAG_ERR_THREAD_CREATE (-30) - 内部库错误。如果你看到这一点,很可能一切都要崩溃了。
- PLCTAG_ERR_THREAD_JOIN (-31) - 另一个内部库错误。你不太可能看到这一点。
- PLCTAG_ERR_TIMEOUT (-32) - 操作花费的时间过长且超时。
- PLCTAG_ERR_TOO_LARGE (-33) - 返回的数据比预期的要多。
- PLCTAG_ERR_TOO_SMALL (-34) - 从远程系统返回的数据不足。
- PLCTAG_ERR_UNSUPPORTED (-35) - 远程系统上不支持该操作。
- PLCTAG_ERR_WINSOCK (-36) - 发生特定于 Winsock 的错误(仅在 Windows 上)。
- PLCTAG_ERR_WRITE (-37) - 尝试写入(通常是套接字)时出错。
- PLCTAG_ERR_PARTIAL (-38) - 收到部分数据或某些内容意外不完整。
- PLCTAG_ERR_BUSY (-39) - 无法执行该操作,因为正在执行其他操作。
- 可以通过如下API函数打印出状态码。它只是把上面的状态码数值转化为等效的字符串。函数返回静态C样式字符串,可以复制它,但不要尝试在程序中释放它。
const char *plc_tag_decode_error(int err);
2.3 Versions and Checking Library Compatibility(版本和检查库的兼容性)
- libplctag开源库的版本由三个部分组成:
- 主要版本(major version):这表示高级API版本。不同的主要版本不保证相互兼容。
- 次要版本(minor version):这表示API的功能级别。API功能不会在此版本升级时被删除。高版本兼容低版本。
- 补丁版本(patch version):记录非API功能的增补或修改。该项添加可能非常重要,比如:增加对Modbus TCP的支持。所以使用时也需要注意。
- 举例:库版本为2.1.9,则2对应主要版本、1对应次要版本、9对应补丁版本。
- 可以使用
plc_tag_check_lib_version()
函数来确认库版本是否满足需求。如果满足需求,函数返回PLCTAG_STATUS_OK
;如果不符合,则返回PLCTAG_ERR_UNSUPPORTED
。
c
#define REQUIRED_VERSION 2,1,0 //程序需要的版本为2,1,0
/* check the library version. */
if(plc_tag_check_lib_version(REQUIRED_VERSION) != PLCTAG_STATUS_OK) {
fprintf(stderr, "Required compatible library version %d.%d.%d not available!", REQUIRED_VERSION);
exit(1);
}
//根据此模块判断是否满足需要
plc_tag_check_lib_version()
函数判断的内部逻辑如下:- 程序需要的主要版本与库的主要版本完全相同
- 程序需要的次要版本小于或等于库的次要版本
- 如果所需的次要版本等于库的版本,则补丁版本小于或等于库的版本;如果所需的次要版本低于库的版本,则忽略。
- 函数如下所示:
c
int plc_tag_check_lib_version(int req_major, int req_minor, int req_patch);
2.4 Tag Life Cycle(标签生命周期)
- 创建 标签句柄。需要知道使用的协议以及该协议所需的参数。访问标签的整套信息包含在一个字符串中,该字符串会被传递给
plc_tag_create()
函数。该字符串的格式与URL类似,由键-值对组成,用&
符号分开。注意 :创建或打开PLC中已存在的标签或字段的句柄,而不是在PLC中创建一个标签。(plc_tag_create_ex()
函数可看API文档)
c
int32_t plc_tag_create(const char *attrib_str, int timeout);
//attrib_str:字符串(以NULL字符结尾),包含创建标签所需的所有信息
//timeout:等待句柄创建完成的值(以毫秒为单位)。如果该值为0,则函数可能会在PLC连接之前返回。
//举例:
int32_t tag = plc_tag_create("protocol=ab_eip&gateway=192.168.1.42&path=1,0&cpu=LGX&elem_count=10&name=myDINTArray[0]", 1000);
- 销毁 标签。标签句柄使用的是内部资源,必须要释放。不能使用
free()
函数。因为标签在内部并非一个内存块,所以要释放标签,需要调用下面的函数。程序要负责标签的创建和销毁。
c
int plc_tag_destroy(int32_t tag);
//返回状态码
- 关闭库 。某些系统在卸载库或关闭程序时,无法触发POSIX或Windows函数。所以,我们需要调用下面的函数。该函数返回后,库会清理所有内部线程和资源,但可以通过创建标签来再次启动库。注意:调用该函数前,必须销毁所有标签。
c
void plc_tag_shutdown(void);
2.5 Callbacks(回调)
2.5.1 Event Callback(事件回调)
- 事件回调API包括三个函数:
c
int plc_tag_register_callback(int32_t tag_id, void (*tag_callback_func)(int32_t tag_id, int event, int status));
int plc_tag_register_callback_ex(int32_t tag_id, void (*tag_callback_func)(int32_t tag_id, int event, int status, void *userdata), void *userdata);
int plc_tag_unregister_callback(int32_t tag_id);
-
每个标签可以注册0个或1个回调函数。
- 如果在已使用回调的标签上再注册,将返回
PLCTAG_ERR_DUPLICATE
。注册成功返回PLCTAG_STATUS_OK
。 - 如果在没有回调的标签上取消注册,将返回
PLCTAG_ERR_NOT_FOUND
。移除成功返回PLCTAG_STATUS_OK
。
- 如果在已使用回调的标签上再注册,将返回
-
库提供了一个在标签上注册回调的函数。以下事件会产生对回调函数的调用:
- PLCTAG_EVENT_CREATED (7) - 标签创建完成后调用回调。创建的最终状态也会传递给回调。在某些情况下,这不太受支持,因此仅依赖于普通标签,而不是像@tags这样的标签。
- PLCTAG_EVENT_READ_STARTED (1) - 已请求读取标记。在调用基础协议实现之前立即调用回调。
- PLCTAG_EVENT_READ_COMPLETED (2) - 读取完成后调用回调。读取的最终状态也会传递给回调。
- PLCTAG_EVENT_WRITE_STARTED (3) - 与读取一样,在请求写入时调用回调。回调可以更改标签中的数据,更改将发送到 PLC。
- PLCTAG_EVENT_WRITE_COMPLETED (4) - 当 PLC 指示写入已完成时调用回调。写入状态将传递给回调。
- PLCTAG_EVENT_ABORTED (5) - 当某些东西调用标签上的 plc_tag_abort() 时调用回调函数。
- PLCTAG_EVENT_DESTROYED (6) - 销毁标记时调用回调函数。此时对标记调用任何 API 函数是不安全的。这纯粹是为了回调来管理任何应用程序状态。
- 本部分理解不全面,具体请读者自行看API文档理解。
2.5.2 Log Callback(日志回调)
- 可以将所有日志记录重定向到自己提供的回调函数。两个API如下:
c
//注册函数
//tag_id:标签句柄
//debug_level:调试级别。不同的调试级别,对应不同的详细程度。具体内容见本文的调试部分。
//message:日志信息的文本字符串。由库管理,不归程序所有,不要释放字符串。如果要存储或更改,需要在副本上操作。
int plc_tag_register_logger(void (*log_callback_func)(int32_t tag_id, int debug_level, const char *message));
//删除注册
int plc_tag_unregister_logger(void);
- 只能注册一个。整个库将使用这个回调来输出日志,而不是默认输出到
stderr
。 - 注意事项:
- 日志回调将从多个线程调用,甚至同时调用。所以代码必须能感知线程和线程安全。
- 日志回调将使用一个或多个互斥锁。不能调用除
plc_tag_decode_error()
以外的任何标签API函数。否则,库可能会挂起。 - 日志记录信息来自库的核心程序。其中许多对延迟非常敏感,不要阻止或延迟将日志回调返回到库的操作。
- message的管理。详见上面的注释。
2.6 Aborting an Operation(中止操作)
- 在某些情况下,使用库的程序可能会决定终止对标签的待定操作。例如,如果标签操作没有设超时值,那么程序会自行超时,并调用
plc_tag_abort()
函数来停止任何正在进行的操作。
c
int plc_tag_abort(int32_t tag);
2.7 Reading a Tag(读标签)
- 读取标签会把读到的数据导入运行库的PC本地内存。数据不会自动更新。如果需要定期查找数据,则需要定期读取标签。
c
int plc_tag_read(int32_t tag, int timeout);
//tag:标签句柄。
//timeout:以毫秒为单位。如果值为0,该函数将设置读取请求并立即返回。如果超时值大于0,则函数等待该毫秒数,以便读取操作完成。如果超时,则中止操作,返回PLCTAG_ERR_TIMEOUT。
- 函数返回状态码(见2.2)。
- 操作成功,返回
PLCTAG_STATUS_OK
- timeout为0,通常返回
PLCTAG_STATUS_PENDING
- 操作失败,返回相应状态码
- 操作成功,返回
2.8 Retrieving Tag Status(获取标签状态)
- 可以使用下面函数获取标签状态。
c
int plc_tag_status(int32_t tag);
//返回当前标签状态的状态码
2.9 Writing a Tag(写标签)
- 可以通过
plc_tag_write()
函数写入目标PLC。但写之前,要通过plc_tag_set_X()
函数设置数据。
c
int plc_tag_write(int32_t tag, int timeout);
3.0 Retrieving/Setting Tag Size(获取/设置标签大小)
3.0.1 获取标签大小
- 下面的函数能获取标签的大小。
c
int plc_tag_get_size(int32_t tag);
- 函数返回标签数据的大小(以字节为单位),或错误状态码(负值)。
3.0.2 设置标签大小
- 该函数设置标签的基础 缓存区大小(以字节为单位)。这是高级API函数调用,通常不使用,除非你使用的是原始CIP标签。
c
int plc_tag_set_size(int32_t tag, int new_size);
- 如果新size大于旧size,则添加的元素可能包含垃圾数据。如果新size小于旧size,则较高的偏移量将被切断,并且该数据永久丢失。
- 函数返回标签内部数据缓冲区的旧size(以字节为单位),或错误状态码(负值)。
3.1 Getting Tag Data Elements(获取标签数据元素)
- get函数:从标签数据的字节偏移量处获取函数名中大小的值。示例如下:
c
//tag:标签句柄
//offset:字节偏移量。比如,64字节的数据,要获取2字节开始的32位数据,则offset为2。
//函数返回32位(4字节)int值
int32_t plc_tag_get_int32(int32_t tag, int offset);
- set函数:执行get函数的相反操作,并将传递的值(使用适当的字节数)放在标签数据中传递的字节偏移量处。
c
int plc_tag_set_int32(int32_t, int offset, int32_t val);
- get函数:无符号(函数名)get函数出错时,返回
UINT_MAX
值,比如:get_uint16(),错误返回65535。有符号get出错时,返回INT_MIN
值。可以用plc_tag_status判断错误或正确。 - set函数:正常返回
PLCTAG_STATUS_OK
,错误返回状态码。 - 各种位数的访问函数、bit操作、string操作、raw byte操作等详见API文档。
3.2 Tag Internal Attribute Functions(标签内部属性函数)
- 函数如下:
c
int plc_tag_get_int_attribute(int32_t tag, const char *attrib_name, int default_value);
int plc_tag_set_int_attribute(int32_t tag, const char *attrib_name, int new_value);
- get函数:必须提供标签句柄、属性名称、找不到属性时返回的默认值。
- set函数:
- 操作正常,函数返回
PLCTAG_STATUS_OK
- 如果标签不支持该属性,则返回
PLCTAG_ERR_NOT_FOUND
- 如果新属性值为非法值,则返回
PLCTAG_ERR_OUT_OF_BOUNDS
- 操作正常,函数返回
- 目前支持以下属性。表格第一列是属性名称,第二列表示该属性可读/可写,第三列表示该属性属于库还是标签,第四列大概说明属性意义。
属性 | R/W | 标签/库 | 意义 |
---|---|---|---|
debug | R/W | 库 | 获取/设置标签的调试级别。该功能与plc_tag_set_debug_level() 重复。 |
version_major | R | 库 | 获取库版本的主版本。例如,库版本为2.5.1,则该属性返回2。 |
version_minor | R | 库 | 获取库版本的次版本。例如,库版本为2.5.1,则该属性返回5。 |
version_patch | R | 库 | 获取库版本的补丁版本。例如,库版本为2.5.1,则该属性返回1。 |
size | R | 标签 | 获取标签使用的大小(以字节为单位)。该功能与plc_tag_get_size() 重复。 |
read_cache_ms | R/W | 标签 | 获取/设置缓存读取结果的时间量(以毫秒为单位)。 |
elem_size | R | 标签 | 获取标签单个元素的大小(以字节为单位)。类比,将标签视为数组,则大小返回1。 |
elem_count | R | 标签 | 如果标签是数组,则返回标签大小(以元素数为单位),否则返回1。 |
3.3 Debugging(调试)
-
使用该库时,有三种方法可以设置调试级别(出于版本历史原因),不过首选第三个:
- 创建标签时向属性字符串添加调试属性:"protocol=XXX&...&debug=4"
- 使用
plc_tag_set_int_attribute() debug
函数设置属性 - 使用
plc_tag_set_debug_level()
函数
-
调试级别如下,数字越大,输出信息越详细越多。请选择合适级别。
- PLCTAG_DEBUG_NONE (0) - 禁用调试输出
- PLCTAG_DEBUG_ERROR (1) - 仅输出错误。一般来说,这些错误对图书馆的运作是致命的。
- PLCTAG_DEBUG_WARN (2) - 输出警告,例如在检查格式错误的标记属性字符串或从 PLC 报告意外问题时发现错误。
- PLCTAG_DEBUG_INFO (3) - 输出有关库中内部调用的诊断信息。包括一些数据包转储。
- PLCTAG_DEBUG_DETAIL (4) - 输出有关库中执行的代码的详细诊断信息,包括数据包转储。
- PLCTAG_DEBUG_SPEW (5) - 输出非常详细的信息。除非您尝试调试有关每个互斥锁和释放的详细信息,否则不要使用它。每毫秒将输出多行输出。