0.13.1 到 1.0.0

Go

qianmoQqianmoQ· 更新于 2026-10-01· 阅读 21 分钟· 0 次阅读

登录后可跨设备保存划线和私人笔记登录

本页涵盖 Go API 的变更。连接串、标签地址与默认值的变更适用于所有语言,详见概述页面——请先阅读那一页。

PLC4Go 在 1.0.0 中的改动比其他任何绑定都大。原因在于一项决定:只返回一个结果的调用直接返回该结果,而每一个可能阻塞的调用都接收一个 context.Context。 连接、关闭与 ping 已不再使用「channel + 结果结构体」的模式。该模式只保留在真正合适的场合——执行请求,因为它确实是异步的。

检查清单

  1. 升级到 Go 1.27。
  2. 将 GetConnection、Connect、Close 和 Ping 改写为「context + error」形式。
  3. 删除 …​WithContext 变体;context 现在是普通调用的第一个参数。
  4. 以同样的方式改写连接缓存的用法。
  5. 替换 logging 包中的级别辅助函数。
  6. 重新检查读取 ArrayInfo.GetUpperBound() 的代码——边界现在是闭区间。
  7. 为连接串中的每个传输层选项加上传输层前缀。
  8. 重新检查基于 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.11.0.0
GetConnection(string) ←chan PlcConnectionConnectResultGetConnection(ctx, string) (PlcConnection, error)
Connect() ←chan PlcConnectionConnectResultConnect(ctx) error
ConnectWithContext(ctx) ←chan …​Connect(ctx) error
Close() ←chan PlcConnectionCloseResultClose() error
BlockingClose()Close() error
Ping() ←chan PlcConnectionPingResultPing(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.11.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.11.0.0
GetConnection(string) ←chan PlcConnectionConnectResultGetConnection(ctx, string) (PlcConnection, error)
GetConnectionWithContext(ctx, string) ←chan …​GetConnection(ctx, string) (PlcConnection, error)
Close() ←chan PlcConnectionCacheCloseResultClose() 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-factor

S7 驱动的机架与插槽同样如此,它们位于 COTP 传输层上:

0.13.1 (PLC4Go)1.0.0
local-rackcotp.local-rack
local-slotcotp.local-slot
remote-rackcotp.remote-rack
remote-slotcotp.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 驱动那样提供订阅能力。

评论

登录后参与评论

正在加载评论…