Go
本页涵盖 Go API 的变更。连接串、标签地址与默认值的变更适用于所有语言,详见概述页面——请先阅读那一页。
PLC4Go 在 1.0.0 中的改动比其他任何绑定都大。原因在于一项决定:只返回一个结果的调用直接返回该结果,而每一个可能阻塞的调用都接收一个 context.Context。 连接、关闭与 ping 已不再使用「channel + 结果结构体」的模式。该模式只保留在真正合适的场合——执行请求,因为它确实是异步的。
检查清单
- 升级到 Go 1.27。
- 将
GetConnection、Connect、Close和Ping改写为「context + error」形式。 - 删除
…WithContext变体;context 现在是普通调用的第一个参数。 - 以同样的方式改写连接缓存的用法。
- 替换
logging包中的级别辅助函数。 - 重新检查读取
ArrayInfo.GetUpperBound()的代码——边界现在是闭区间。 - 为连接串中的每个传输层选项加上传输层前缀。
- 重新检查基于
ProvidesSubscribing/ProvidesBrowsing分支的代码。
Go 1.27
plc4go 现在要求 Go 1.27。
这一版本下限换来了一项依赖的移除:原先从 github.com/google/uuid 引入的 uuid 包如今由标准库提供,因此该依赖已彻底从 go.mod 和 go.sum 中移除。
连接、关闭与 ping
PlcConnection 和 PlcDriverManager 的形态发生了变化。PlcConnectionConnectResult 和 PlcConnectionCloseResult 类型已被移除,而 PlcConnection 实现了 io.Closer。
| 0.13.1 | 1.0.0 |
|---|---|
GetConnection(string) ←chan PlcConnectionConnectResult | GetConnection(ctx, string) (PlcConnection, error) |
Connect() ←chan PlcConnectionConnectResult | Connect(ctx) error |
ConnectWithContext(ctx) ←chan … | Connect(ctx) error |
Close() ←chan PlcConnectionCloseResult | Close() error |
BlockingClose() | Close() error |
Ping() ←chan PlcConnectionPingResult | Ping(ctx) error |
Discover(cb, opts…) | Discover(ctx, cb, opts…) |
DiscoverWithContext(ctx, cb, opts…) | Discover(ctx, cb, opts…) |
调用点的形式也随之改变:
// 0.13.1
driverManager := plc4go.NewPlcDriverManager()
drivers.RegisterModbusTcpDriver(driverManager)
connectionRequestChanel := driverManager.GetConnection("modbus-tcp://192.168.23.30")
connectionResult := <-connectionRequestChanel
if connectionResult.GetErr() != nil {
fmt.Printf("error connecting: %s", connectionResult.GetErr().Error())
return
}
connection := connectionResult.GetConnection()
defer connection.BlockingClose()// 1.0.0
driverManager := plc4go.NewPlcDriverManager()
drivers.RegisterModbusTcpDriver(driverManager)
ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
defer cancel()
connection, err := driverManager.GetConnection(ctx, "modbus-tcp://192.168.23.30")
if err != nil {
fmt.Printf("error connecting: %s", err.Error())
return
}
defer connection.Close()defer connection.Close() 是 BlockingClose() 存在的目的所在。它现在就是地道的 Go 代码行,而 go vet 也能理解它。 |
|---|
|
Invalidate()
PlcConnection 新增了 Invalidate(),它会将连接标记为不可恢复的失败状态,使缓存无需健康检查即可丢弃该连接。你不必调用它——但如果你的代码以 PLC4Go 无法察觉的方式检测到连接已失效,在归还租约之前调用它,可以为缓存省去一次 ping,也为下一个调用方省去一次失败的请求。
传输层错误现在已被分类,并通过编解码器与传输层进行传播(TransportErrorKind),因此驱动本身会在可行的情况下做出这一调用。
执行请求
请求执行仍然基于 channel——这确实是异步的——但 …WithContext 变体已经移除。上下文被移入了普通调用中:
| 0.13.1 | 1.0.0 |
|---|---|
Execute() | Execute(ctx) |
ExecuteWithContext(ctx) | Execute(ctx) |
ExecuteWithInterceptor(fn) | ExecuteWithInterceptor(ctx, fn) |
ExecuteWithInterceptorWithContext(ctx, fn) | ExecuteWithInterceptor(ctx, fn) |
这同样适用于读取、写入、订阅、取消订阅和浏览请求。
// 0.13.1
readResult := <-readRequest.Execute()
// 1.0.0
readResult := <-readRequest.Execute(ctx)如果调用点没有有意义的上下文,context.Background() 也能编译通过——但设置截止时间几乎总是更好的选择,而且现在它会被正确遵守:
超时会被报告为超时
耗尽时间的请求会被报告为超时,而不是 INTERNAL_ERROR。这既涵盖驱动自身的请求超时,也涵盖你在传入的 context 上设置的截止时间。
那些为了区分超时与真实失败(以决定是否重试)的代码,现在能看到它一直期待的超时。那些把 INTERNAL_ERROR 当作「多半是超时」来处理的代码,应该停止这种做法了。
PLC4Go 中的每个超时现在也都带有名称(utils.WithNamedTimeout),因此过期时会说明是哪个超时。
连接缓存
缓存遵循同样的重写方式:
| 0.13.1 | 1.0.0 |
|---|---|
GetConnection(string) ←chan PlcConnectionConnectResult | GetConnection(ctx, string) (PlcConnection, error) |
GetConnectionWithContext(ctx, string) ←chan … | GetConnection(ctx, string) (PlcConnection, error) |
Close() ←chan PlcConnectionCacheCloseResult | Close() error |
WithMaxResponseGrabTimeout(d) | 已移除 - 见下文 |
WithMaxIdleTime(d)(新增) |
// 1.0.0
cache := cache.NewPlcConnectionCache(driverManager,
cache.WithMaxLeaseTime(30*time.Second),
cache.WithMaxIdleTime(5*time.Minute),
)
defer cache.Close()
connection, err := cache.GetConnection(ctx, "modbus-tcp://192.168.23.30")
if err != nil {
return err
}
defer connection.Close()WithMaxResponseGrabTimeout 已被移除,因为不再需要从 channel 获取响应。
WithMaxIdleTime 是新增的:它会丢弃空闲时间超过给定时长的缓存连接,并在下次租借时重新建立连接(0 表示永不过期,为默认值)。可将其用于那些会静默回收空闲连接的远端——半开的 TCP 连接在第一次写入失败之前是无法被检测到的。报告了活跃订阅句柄的连接不适用该 TTL,因为它们的订阅状态保存在连接上。
logging 包
pkg/api/logging 不再设置或重置全局 zerolog 级别。ErrorLevel()、WarnLevel()、InfoLevel()、DebugLevel()、TraceLevel() 和 ResetLogging() 均已被移除,同时移除的还有 init(),它曾在该包被导入时立即将全局 logger 强制设为 error 级别。
init() 才是真正的问题所在:导入一个 PLC4X 包会改变整个进程的日志行为。
请改为向 PLC4X 传入一个 logger,这种方式是显式的,并且作用域仅限于该连接:
// 1.0.0
logger := zerolog.New(os.Stderr).Level(zerolog.DebugLevel)
connection, err := driverManager.GetConnection(ctx, connectionString,
options.WithCustomLogger(logger))如果您之前依赖 PLC4X 为您静默 zerolog,请自行设置日志级别:
zerolog.SetGlobalLevel(zerolog.ErrorLevel)连接字符串选项需要带传输前缀
PLC4Go 读取传输选项时不带前缀,而 PLC4J 在传输上声明这些选项,并且文档中的每个示例都使用带前缀的写法。因此,文档中给出的连接字符串在 PLC4Go 中实际上没有设置任何选项,而且也没有任何地方说明这一点。
现在传输选项需要通过传输自身的代码来指定,而不带前缀的名称会被报告为未知项:
tcp.connect-timeout-ms
serial.baud-rate
udp.so-reuse
pcap.speed-factorS7 驱动的机架与插槽同样如此,它们位于 COTP 传输层上:
| 0.13.1 (PLC4Go) | 1.0.0 |
|---|---|
local-rack | cotp.local-rack |
local-slot | cotp.local-slot |
remote-rack | cotp.remote-rack |
remote-slot | cotp.remote-slot |
驱动自身注入到映射中的选项(defaultTcpPort)不涉及任何命名规则,保留其原始名称。
OPC UA 选项
PLC4Go 的 OPC UA 驱动读取的名称源自其自身的 Go 结构体字段(keyStoreFile、securityPolicy),而非 PLC4J 声明并在文档中列出的名称。现在它读取的是 tls.keystore、tls.keystore-password、security-policy 和 allow-unverified-security-policies,与其他选项保持一致。
它也不再遇到未知选项时拒绝连接,而是像其他所有驱动一样给出警告——因此,PLC4J 接受的连接字符串在 Go 中也不再被拒绝。
ArrayInfo 边界为闭区间
GetSize() == GetUpperBound() - GetLowerBound() + 1它们在 PLC4Go 中是独有的,并被记录为一处有意的偏离,因此 [0..7] 在 Java 中报告八个元素,在 Go 中报告七个——这正是共享数组语法旨在消除的、针对同一地址的分歧。
直接读取 GetUpperBound() 的代码必须重新审视。不会出现编译失败;只是这个数字与原来相差 1。 |
|---|
ArrayInfo 还新增了 GetBase() 和 IsRange()。Go 没有默认方法,因此 PLC4Go 之外的任何接口实现都必须添加它们。
标签地址
概述页]中描述的数组语法变更同样适用于 PLC4Go,并且 Go 解析器直接使用 Java 的用例进行测试——现在同一个地址在两种语言中含义相同。
有两个 Go 驱动改变了仍然能够解析的地址的含义,因此没有需要拒绝的内容,也没有运行时告警:
- ADS:
[n]原本是 n 个元素的数量,现在是索引为 n 的元素。MAIN.g_arr[3]原本读取三个元素,现在读取一个。请改写为MAIN.g_arr[0..2]。ADS 还移除了[a:b]这种起始加数量的形式,PLC4J 从来没有过:MAIN.g_arr[2:4]现在写作MAIN.g_arr[2..5]。 - Firmata:
[n]原本是连续的 n 个引脚,现在是索引为 n 的引脚。digital:2[3]原本从 2 号引脚开始读取三个引脚,现在读取 5 号引脚。请改写为digital:2[0..2]。
数量为零不再有任何写法。若干 Go 驱动曾接受 [0],却以“数量必须大于零”为由拒绝它;现在 [0] 用于选择第一个元素。
驱动反向渲染地址时,现在的写法与其解析器读取的写法一致。此前有若干地址无法往返——BACnet/IP 在语法要求 , 的地方渲染出 :,KNXnet/IP 设备地址在语法要求 . 的地方渲染出 /,而 ADS 的直接形式会把索引组以十进制数字打印在 0x 前缀之后,于是 16416 回来时变成了 0x16416,成了另一个地址。
值的序列化方式不同
如果你解析某个 PlcValue 的序列化形式,以下内容会发生变化:
PlcDWORD、PlcSINT、PlcULINT和PlcWSTRING此前分别序列化为PlcDINT、PlcINT、PlcUINT和PlcSTRING。现在它们使用各自的名称。PlcTIME和PlcLTIME会以包含时、分、秒以及亚秒小数的 ISO-8601 格式渲染,而不再截断到整秒。PlcDATE_AND_TIME以 ISO-8601 格式渲染 UTC 挂钟时间,而不是 Go 的本地时区默认值。PlcStruct保持确定的成员顺序。- 类字符串的值携带
encoding="UTF-8"。
另外,PlcDATE_AND_TIME.GetDayOfWeek() 返回 PLC4J 所返回的编号——周一为 1、周日为 7——而不是 Go 的 time.Weekday,后者将周日记为 0。在 KNX DPT 19.001 中,0 表示“未指定日期”,而对 S7 来说它根本无效。
PlcDATE 和 PlcTIME_OF_DAY 现在会暴露其各个组成部分,而不再只有整体值。
串口传输
- 默认波特率从 115200 改为 9600,与常见的串口默认值以及 Java 传输层保持一致。如果你依赖之前的默认值,请显式指定
serial.baud-rate。 - 未显式设置上下文超时的读写操作,现在受新的
serial.read-timeout-ms/serial.write-timeout-ms选项限制(默认 1000 ms;设为 0 则恢复之前阻塞式的行为)。 - 无效的串口选项值现在会导致连接创建失败,而不是被静默忽略。
传输层新增了连接字符串中的全套串口选项——data-bits、stop-bits、parity、flow-control、dtr、rts——以及共享端口操作(reuse-port,用于多从站 Modbus RTU)和帧间写入间隔控制(interframe-delay)。
连接元数据
EtherNet/IP 和 Modbus 连接此前将 ProvidesSubscribing 和 ProvidesBrowsing 留在其零值上,因而报告的 false 是偶然结果,而非有意为之。现在两者都会如实声明自身支持的能力。基于这些标志位做分支的代码将得到不同——且正确——的结果。
1.0.0 新增内容
这些不属于迁移工作,但却是升级 PLC4Go 通常物有所值的原因:
- 五个新驱动:AB-Ethernet、Firmata、IEC 60870-5-104、SLMP(MELSEC)和 UMAS,使 PLC4Go 的驱动数量从九个增至十四个。AB-Ethernet 和 Firmata 的实现与 Java 中一样属于部分支持;IEC 60870-5-104 只支持订阅、不支持其他操作,因为该协议是推送驱动的。
- 生产级 BACnet/IP 驱动,支持分段、写优先级、定向及多目标 WhoIs、路由寻址以及数组/位串属性解码。
- EtherNet/IP 提升至与 Java 驱动同等水平:完整的三条读写路径、UDP 广播发现、
logix驱动别名,以及bigEndian、forceUnconnectedOperation、communicationPath和connectionSerialNumber选项。 - S7 新增浏览、告警和周期性订阅、真正的往返 ping,以及对 S5TIME、变长字符串和告警标签地址的解析。
- Modbus RTU 和 ASCII 拥有了各自的编解码器,补齐了与 Java 驱动在标签、数值和配置方面的差距。
- KNXnet/IP 支持写入组地址,其订阅功能也可用。
- 基于轮询的订阅已成为默认连接集合的一部分,因此即使某驱动的协议本身没有订阅机制,也可以像 Java 驱动那样提供订阅能力。
评论
登录后参与评论
KnowForge