omniplc
介绍
omniplc:一个面向多品牌、多协议 PLC 的 Python 统一通信库。一次编写,即可通过一致的 API 对接三菱、欧姆龙、基恩士、汇川、松下、丰田、罗克韦尔(AB)、倍福(TwinCAT)、西门子(S7)等 PLC/扫码枪、OPC-UA 服务器与 CNC 机床(MTConnect),支持 Modbus、MC(3E/4E/1E 以太网帧、1C/3C/4C 串口帧)、FINS、NJ/NX CIP(EtherNet/IP)、KV Host Link、KV MC 协议兼容(SLMP)、汇川 H3U/H5U(Modbus TCP/RTU、MC 协议兼容 3E)、松下 FP(MC 协议兼容 3E、MEWTOCOL)、SR、TOYOPUC 计算机链接、EtherNet/IP(Logix 标签读写)、TwinCAT ADS(封装 pyads)、西门子 S7(封装 python-snap7,DB/I/Q/M)、通用自定义 TCP(分隔符成帧)、OPC-UA、MTConnect 数采等协议。
- Python 3.7+,uv 开发,核心零第三方依赖
- 命名与使用习惯对齐,迁移成本极低
- 全量类型标注(PEP 484 + py.typed),mypy 检查通过
- 内置全局报文调试开关(
omniplc.set_debug(True)一键输出所有协议的请求/响应报文) - 线程安全、惰性自动重连、可配置超时/重试
- 同步 + 异步(异步类 = 同步类名前加
A)双轨 API
类继承图
BaseClient(ABC,模板方法:连接状态机 / 事务锁 / 惰性重连 / 类型化读写只写一次)
│
├── ModbusBaseClient —— 寄存器级公共逻辑:字序 / 类型分发 / 范围校验
│ ├── ModbusTcpClient —— MBAP over TCP(502)
│ │ └── InovanceTcpClient —— 汇川 H3U/H5U,继承 Modbus 只换软元件地址映射
│ └── ModbusRtuClient —— 站号+PDU+CRC16 over 串口(需 pyserial)
│ └── InovanceRtuClient —— 汇川 H3U/H5U RTU(9600-8N2),继承 Modbus 只换地址映射
│
├── MelsecMcTcpClient —— 三菱 MC 3E/4E/1E 帧 over TCP(2000)
│ ├── KeyenceMcTcpClient —— 基恩士 KV MC 兼容 / SLMP 3E(5000),只换码表
│ ├── InovanceMcTcpClient —— 汇川 MC 兼容 3E,换码表 + 记号换算(S→L、R=D+8000、X/Y 八进制)
│ └── PanasonicMcTcpClient —— 松下 FP0H/FP7 MC 兼容 3E,换码表 + 记号换算(字号×16+位号、R9000+→SM、D90000+→SD)
├── MelsecMcUdpClient —— 三菱 MC 同帧型 over UDP(2000)
│ └── KeyenceMcUdpClient —— 基恩士 KV MC 兼容 SLMP 3E over UDP(5000),与 TCP 版共用码表覆写
├── MelsecMcSerialClient —— 三菱 MC 串口帧(C24):1C A 兼容 ASCII 格式 4 / 3C ASCII 格式 4 / 4C 二进制格式 5,需 pyserial
├── MelsecMxClient —— 三菱 MX Component(Windows,comtypes,逻辑站号)
│
├── OmronFinsTcpClient —— 欧姆龙 FINS + TCP 握手(9600)
├── OmronFinsUdpClient —— 欧姆龙 FINS over UDP(9600)
├── AllenBradleyEthIpClient —— 罗克韦尔 AB EtherNet/IP(44818):Logix 标签自描述,unconnected/connected 双通道
│ └── OmronCipClient —— 欧姆龙 NJ/NX CIP(44818):unconnected 直发无背板路由,NJ 变量读写
├── BeckhoffAdsClient —— 倍福 TwinCAT ADS(AMS 851,封装 pyads):变量名即地址,ADSError 不断线
├── KeyenceHostLinkTcpClient —— 基恩士 KV Host Link over TCP(8000)
├── KeyenceHostLinkUdpClient —— 基恩士 KV Host Link over UDP(8000)
├── KeyenceSrClient —— 基恩士 SR 扫码枪 TCP(9004,LON/LOFF 触发扫码)
├── PanasonicMewtocolTcpClient —— 松下 MEWTOCOL over TCP(1024):ASCII 帧 + BCC,RCS/WCS 单接点、RD/WD 数据区
├── PanasonicMewtocolUdpClient —— 松下 MEWTOCOL over UDP(1024)
├── ToyopucTcpClient —— 丰田 TOYOPUC 计算机链接 over TCP(1025)
├── ToyopucUdpClient —— TOYOPUC 同帧 over UDP(1025)
├── OpcUaClient —— OPC-UA opc.tcp 会话(4840,封装 asyncua)
├── MTConnectClient —— CNC 机床数采(HTTP/XML 只读,Agent 默认 5000)
├── SiemensS7Client —— 西门子 S7(102,rack/slot 路由,封装 python-snap7)
└── OpenTcpClient —— 通用自定义 TCP/IP(端口按设备):分隔符/定长成帧 + 内部缓冲,send/receive/transact*
异步镜像(omniplc.aio):类名 = 同步类名前加 A,签名同名同型,共 28 个
AModbusTcpClient / AModbusRtuClient / AInovanceTcpClient / AInovanceRtuClient / AInovanceMcTcpClient
AMelsecMcTcpClient / AMelsecMcUdpClient / AMelsecMcSerialClient / AMelsecMxClient / AOmronFinsTcpClient
AOmronFinsUdpClient / AOmronCipClient / ABeckhoffAdsClient / AAllenBradleyEthIpClient / AKeyenceHostLinkTcpClient
AKeyenceHostLinkUdpClient / AKeyenceMcTcpClient / AKeyenceMcUdpClient / APanasonicMcTcpClient / APanasonicMewtocolTcpClient
APanasonicMewtocolUdpClient / AKeyenceSrClient / AToyopucTcpClient / AToyopucUdpClient / AOpcUaClient
AOpenTcpClient / AMTConnectClient / ASiemensS7Client
详细架构设计见 docs/architecture.md。
安装
uv add omniplc # 或 pip install omniplc
uv add 'omniplc[serial]' # 需要 Modbus RTU 或三菱 MC 串口 3C/4C 帧时(pyserial)
uv add 'omniplc[mx]' # 需要三菱 MX Component(Windows)时
uv add 'omniplc[opcua]' # 需要 OPC-UA 时(安装 asyncua)
uv add 'omniplc[ads]' # 需要倍福 TwinCAT ADS 时(安装 pyads,Windows 需 TcAdsDll 运行库)
uv add 'omniplc[s7]' # 需要西门子 S7 时(安装 python-snap7,按解释器版本自动二选一)
快速上手
from omniplc import ModbusTcpClient, DataType
client = ModbusTcpClient(ip_address="192.168.0.10", port=502, station=1)
client.receive_timeout = 3.0 # 超时走属性,不进构造函数
client.connect()
ok, value = client.read_float("hr100") # 读返回 (是否成功, 值)
ok = client.write_float("hr100", 3.14) # 写返回 bool
print(client.last_error) # 失败原因在这里
client.disconnect()
# 上下文管理器:进入自动连接,失败抛 ConnectionError
with ModbusTcpClient("192.168.0.10", 502, 1) as client:
results = client.read_many(["hr0", "hr2"], DataType.FLOAT) # [(是否成功, 值), ...] 逐点列表
ok, value = results[0]
# 通用 read/write 推荐传 DataType 枚举(IDE 自动补全),也兼容字符串
ok, value = client.read("hr0", DataType.FLOAT)
# 掩码写(FC22):设备侧原子位修改,替代读-改-写两段事务
ok = client.write_mask_register("hr100", and_mask=0xFFFE, or_mask=0x0001)
# Modbus RTU(串口)
from omniplc import ModbusRtuClient
rtu = ModbusRtuClient(station=1)
rtu.configure_serial("COM3", baud_rate=9600)
三菱 / 欧姆龙 / 基恩士 / 汇川 / 丰田
from omniplc import MelsecMcTcpClient, OmronFinsUdpClient, McFrame
# 三菱 MC:frame=McFrame.FRAME_3E/FRAME_4E(QnA 兼容)或 FRAME_1E(A 兼容,A 系列)
mc = MelsecMcTcpClient(ip_address="192.168.3.39", port=2000, frame=McFrame.FRAME_3E)
mc.connect()
ok, value = mc.read_ushort("D100")
ok = mc.write_bool("M100", True)
ok, values = mc.read_batch([("D100", "short"), ("M100", "bool")]) # 0406 多块批量读,单事务
# 三菱 MC 串口帧(C24 串口模块,需 pyserial):1C=A 兼容 ASCII 格式 4(BR/WR/BW/WW),
# 3C=QnA 兼容 ASCII 格式 4,4C=二进制格式 5
# 软元件地址与 3E 帧一致;串口参数须与 C24"传送设定"一致,默认访问连接站 CPU(PC 号 FF)
from omniplc import MelsecMcSerialClient
mc_sio = MelsecMcSerialClient(frame=McFrame.FRAME_4C)
mc_sio.configure_serial("COM3", baud_rate=9600)
mc_sio.connect()
ok, value = mc_sio.read_ushort("D100")
ok = mc_sio.write_bool("M100", True)
# 三菱 MX Component(Windows):通信参数在通信设置实用程序中配置为逻辑站号
# 安装:pip install 'omniplc[mx]'
from omniplc import MelsecMxClient
mx = MelsecMxClient(logical_station_number=1)
mx.connect()
ok, value = mx.read_ushort("D100")
ok = mx.write_bool("M10", True)
# ReadDeviceRandom 原生随机读:仅 16 位类型(BOOL/SHORT/USHORT),单事务
ok, values = mx.read_batch([("M10", "bool"), ("D100", "short")])
# 欧姆龙 FINS:TCP 自动做节点分配握手,UDP 无握手
fins = OmronFinsUdpClient(ip_address="192.168.250.1", port=9600)
fins.connect()
ok, value = fins.read_ushort("D100")
ok = fins.write_bool("CIO0.5", True)
ok, values = fins.read_batch([("D100", "short"), ("CIO0.5", "bool")]) # 0104 多存储区读,单事务
# 欧姆龙 NJ/NX CIP(内置 EtherNet/IP):Sysmac 变量自描述,地址即变量名
# TestVar / MyArray[5] / Motor[2].Speed;继承 AB 客户端,直发不包 UC Send
from omniplc import OmronCipClient
nj = OmronCipClient(ip_address="192.168.0.10", port=44818)
ok, value = nj.read_int("TestVar")
ok = nj.write_bool("RunFlag", True)
nj_c = OmronCipClient(ip_address="192.168.0.10", connected_messaging=True) # 连接型
# 倍福 TwinCAT(ADS):变量名即地址(MAIN.nCounter / .gGlobal / GVL.MyVar)
# 安装:pip install 'omniplc[ads]'(Windows 侧还需 Beckhoff TcAdsDll 运行库,随 TwinCAT ADS 安装)
from omniplc import BeckhoffAdsClient
bc = BeckhoffAdsClient(ip_address="192.168.0.10", ads_port=851) # net_id 默认 IP+.1.1
ok, value = bc.read_int("MAIN.nCounter")
ok = bc.write_bool("MAIN.bStart", True)
ok, text = bc.read_string("MAIN.sRecipe")
# 罗克韦尔 AB EtherNet/IP:Logix 标签自描述,类型不符会明确报错
# 地址即标签名:MyDint / MyArray[5] / MyUdt.Member / MyDint.3(位)/ 程序作用域 Program:prog.Tag
from omniplc import AllenBradleyEthIpClient
ab = AllenBradleyEthIpClient(ip_address="192.168.1.20", port=44818, slot=0)
ab.connect()
ok, value = ab.read_int("MyDint")
ok = ab.write_bool("StartCmd", True)
ok, text = ab.read_string("RecipeName")
# 0x0A 多服务包:一帧混读多个标签(上限 32 条;BOOL 首次批量读会做一次类型发现)
ok, values = ab.read_batch([("MyDint", "int"), ("MyReal", "float"), ("RecipeName", "string")])
ok, info = ab.get_plc_info() # Identity Object:厂商/序列号/产品名
ok, ident = ab.list_identity() # ENIP 单播发现
# connected 消息(Forward Open + SendUnitData,大批量轮询吞吐更高):
ab_c = AllenBradleyEthIpClient(ip_address="192.168.1.20", connected_messaging=True)
# 基恩士 KV Host Link:ASCII 行式协议,地址如 DM100 / R515 / W100
from omniplc import KeyenceHostLinkTcpClient
kv = KeyenceHostLinkTcpClient(ip_address="192.168.0.10", port=8000)
kv.connect()
ok, value = kv.read_ushort("DM100")
ok = kv.write_bool("R515", True)
# 基恩士 KV MC 协议兼容(SLMP):二进制 3E 帧复用三菱实现,仅码表不同
# 地址如 R5(位,十进制)/ DM100(字,十进制)/ B1F / W10(十六进制)/ ZR100
from omniplc import KeyenceMcTcpClient, KeyenceMcUdpClient
kvmc = KeyenceMcTcpClient(ip_address="192.168.1.22", port=5000)
ok, value = kvmc.read_ushort("DM100")
ok = kvmc.write_bool("R5", True)
kvmc_u = KeyenceMcUdpClient(ip_address="192.168.1.22", port=5000) # UDP 走线,一问一答一数据报
# 汇川 H3U/H5U:Modbus TCP/RTU + 汇川软元件地址映射
# 地址如 D100 / R100 / M10 / SM10 / X17(八进制)/ D100.3;T/C 位=接点、字=当前值
from omniplc import InovanceTcpClient, InovanceRtuClient
h3u = InovanceTcpClient(ip_address="192.168.1.88", port=502, station=1)
ok, value = h3u.read_ushort("D100")
ok = h3u.write_bool("M10", True)
rtu2 = InovanceRtuClient(station=1)
rtu2.configure_serial("COM3") # 汇川缺省 9600-8N2
# 汇川 MC 协议兼容(3E 帧,Easy 系列/H5U 固件 V6.4.0.0+ 的"MC配置"功能)
# 帧按三菱口径编码;S 按三菱 L 码、R 与 D 统一编址(R100=D8100)、X/Y 八进制命名
# 端口无出厂默认,须与 AutoShop"MC配置"中设置一致
from omniplc import InovanceMcTcpClient
imc = InovanceMcTcpClient(ip_address="192.168.1.88", port=2000)
ok, value = imc.read_ushort("R100")
ok = imc.write_bool("X17", True)
# 松下 FP0H/FP7:MC 协议兼容(3E 帧,仅二进制成批读/写)+ MEWTOCOL(TCP/UDP)
# MC:位软元件按"字号+位号"(R1F=字1位F/R1.15),R9000+(字900)起为 SM,D90000+ 为 SD
# 端口以模块配置为准;MEWTOCOL 接点 X/Y/R/T/C/L + 数据 D/L/F/S/K(K=经过值,S=设定值)
from omniplc import PanasonicMcTcpClient, PanasonicMewtocolTcpClient, PanasonicMewtocolUdpClient
pmc = PanasonicMcTcpClient(ip_address="192.168.0.10", port=2000)
ok, value = pmc.read_ushort("D100")
ok = pmc.write_bool("R1F", True)
mew = PanasonicMewtocolTcpClient(ip_address="192.168.0.10", port=1024, station=1)
ok, value = mew.read_ushort("D100")
ok = mew.write_bool("Y0.3", True)
mewu = PanasonicMewtocolUdpClient(ip_address="192.168.0.10", port=1024)
# 基恩士 SR 扫码枪:触发式设备,scan() 返回 (是否读到, 条码文本)
from omniplc import KeyenceSrClient
sr = KeyenceSrClient(ip_address="192.168.0.10", port=9004, scan_dwell=1.0)
sr.connect()
ok, code = sr.scan() # LON → 窗口 → LOFF → 读应答
# 丰田 TOYOPUC 计算机链接:二进制帧,地址如 D0100 / M0201 / X0010H / M0201W
from omniplc import ToyopucTcpClient
toyopuc = ToyopucTcpClient(ip_address="192.168.0.10", port=1025)
ok, value = toyopuc.read_ushort("D0100") # 编号为十六进制
ok = toyopuc.write_bool("M0201", True) # 位软元件 CMD=20/21 直读直写
# OPC-UA:标准 NodeId 寻址,读写按显式数据类型编解码;入口同其他客户端(IP+端口)
# 安装:pip install 'omniplc[opcua]'
from omniplc import OpcUaClient
opc = OpcUaClient("192.168.0.10", 4840)
ok, value = opc.read_float("ns=2;s=Device.Temperature")
ok = opc.write_ushort("ns=2;s=Device.Speed", 1200)
# 通用自定义 TCP:任意分隔符成帧设备(称重仪表、传感器、自定义程序等)
# 分隔符/编码/收发行为构造期可配;超时与重连沿用属性(client.receive_timeout / client.retries)
from omniplc import OpenTcpClient
dev = OpenTcpClient(ip_address="192.168.0.10", port=9000, delimiter="\r\n")
dev.receive_timeout = 2.0
dev.connect()
ok = dev.send_text("READ") # 自动补分隔符(append_delimiter 可关)
ok, raw = dev.receive() # 按分隔符收一帧(bytes),跨分片自动拼接
ok, text = dev.transact_text("VER") # 发送并收一帧(str);坏帧/解码失败断线重连,超时不断线
# CNC 机床数采(MTConnect):数据项 id 即地址,Agent 默认端口 5000(标准库实现,零第三方依赖)
# FANUC/三菱等控制器经适配器喂给 Agent 即可采;先 snapshot() 查看机器实际提供的数据项
from omniplc import MTConnectClient
cnc = MTConnectClient("192.168.0.10", 5000)
cnc.connect()
ok, speed = cnc.read_float("Sspeed") # 主轴转速(文本值自动转 float)
ok, program = cnc.read_string("program")
ok, items = cnc.snapshot() # 全量当前值快照 {数据项 id: 文本值}
ok, alarms = cnc.read_conditions() # 条件项(Fault/Warning/Normal)列表
ok, device = cnc.probe() # 设备信息(name/uuid 等)
# 西门子 S7(封装 python-snap7):DB/I/Q/M 绝对寻址,ISO-on-TCP 102,rack/slot 路由
# S7-1200/1500 需勾选"允许来自远程对象的 PUT/GET 通信访问",DB 须为非优化块
# 安装:pip install 'omniplc[s7]' —— 依赖按 Python 版本自动二选一:
# 3.7~3.9 → python-snap7 1.3(C 封装,64 位用捆绑库;32 位需自备 snap7.dll 经 dll_path 指定)
# 3.10+ → python-snap7 3.x(纯 Python 实现,无需原生 DLL)
from omniplc import SiemensS7Client
s7 = SiemensS7Client("192.168.0.1", rack=0, slot=1) # 300/400 的 CPU 常在槽位 2
s7.connect()
ok, temp = s7.read_float("DB1.DBD6") # DB 双字起点,REAL
ok = s7.write_bool("DB1.DBX0.3", True) # DB 位(锁内读-改-写)
ok, current = s7.read_ushort("MW10") # Merker 字
ok, text = s7.read_string("DB1.DBS20", length=32) # S7 String(头 2 字节声明/实际长)
批量读取(协议原生,单事务)
read_many(地址列表, 数据类型) 与 read_batch([(地址, 类型), ...]) 全库统一契约:一帧往返读回多个点(不是循环单点),任一地址非法或设备拒绝则整批失败(原因在 last_error;要逐点容错请逐点 read)。以下驱动覆写为协议原生批量,其余驱动回退逐点独立事务:
- 三菱 MC 3E/4E:
0406多块批量读(混软元件,总块数 ≤120);KV/汇川/松下 MC 兼容子类经继承同享 - 欧姆龙 FINS:
0104多存储区读(以太网上限 167 条) - AB / 欧姆龙 NJ-NX CIP:
0x0A多服务包(上限 32 条;BOOL 首次批量读做一次类型发现后缓存) - OPC-UA:UA Read 服务原生多节点(asyncua
read_values单请求) - MX Component:
ReadDeviceRandom(软元件列表换行分隔;仅 16 位类型)
# 混类型混软元件:一帧取回全部值(以 MC 为例,其余驱动同款接口)
ok, values = mc.read_batch([
("D100", "short"), ("D102", "float"), ("M100", "bool"), ("D110.3", "bool"),
])
# 统一类型批量:read_many(等价于 read_batch 的同类型版本)
ok_list = mc.read_many(["D0", "D2", "D4"], DataType.FLOAT)
报文调试(全局开关)
import omniplc
omniplc.set_debug(True) # 之后所有协议客户端输出请求/响应报文(十六进制)
omniplc.set_debug(False) # 关闭
- 走线型协议(TCP/UDP/串口)在传输层统一挂钩,输出每次收发的原始字节;
TCP 应答分多段到达时按段输出。输出形如
tcp://192.168.0.10:2000 → 发送 12B: 50 00 00 FF …(单条最多转储 4096B) - 会话型协议(OPC-UA / ADS / MX Component 无字节流)输出操作级日志,
如
ads://192.168.0.10.1.1:851 读 MAIN.rTemp(PLCTYPE_REAL) → 3.14 - 输出走
logging(记录器名omniplc.debug,DEBUG 级):应用已配置 logging 时自动汇入既有日志体系;未配置时自动挂 stderr 处理器,开箱即用 - 进程级开关,同步与异步客户端共用;连接建立/断开也会输出,便于观察惰性重连
异步(类名前加 A)
import asyncio
from omniplc.aio import AModbusTcpClient
async def main():
client = AModbusTcpClient("192.168.0.10", 502, 1)
await client.connect()
ok, value = await client.read_float("hr100")
await client.disconnect()
asyncio.run(main())
异步客户端是多设备并发的手段,不是单连接提速(单设备逐笔轮询用同步即可,异步只多线程切换开销): 协议事务在单连接内本就是"一问一答"串行,收益来自把多台设备的等待重叠——10 台设备并发采集约等于顺序轮询的 1/10 耗时。
import asyncio
from omniplc.aio import AMelsecMcTcpClient, AOmronFinsTcpClient, AAllenBradleyEthIpClient
async def main():
clients = [
AMelsecMcTcpClient("192.168.0.11", 2000),
AOmronFinsTcpClient("192.168.0.12", 9600),
AAllenBradleyEthIpClient("192.168.0.13", 44818),
]
for client in clients:
await client.connect()
# 三台设备的同时刻采集:总耗时 ≈ 最慢一台的往返,而非三者之和
d1, d2, d3 = await asyncio.gather(
clients[0].read_float("D100"),
clients[1].read_float("D100"),
clients[2].read_float("MyReal"),
)
for client in clients:
await client.disconnect()
asyncio.run(main())
点位表(可选)
from omniplc import TagTable
# tags.json 格式:[{"tag_id": "furnace_temp", "address": "D100", "data_type": "float",
# "scale": 0.1, "remark": "炉温"}, ...]
# tag_id 为程序用标识(一般字母/数字),remark 为中文备注(供人员查看记录,可省略)
client.bind_tags(TagTable.from_json("tags.json"))
ok, value = client.read_tag("furnace_temp") # 点位标识 → 地址+类型,自动应用缩放
错误处理约定
读返回 (bool, 值),写返回 bool,不抛自定义异常;
失败原因记录在 client.last_error(含 PLC 原始错误码)。参数非法(地址/类型/
范围错误)抛 ValueError。批量操作逐点独立容错,单点失败不影响其他点。
失败分类与原始码(v0.34.0 起):除 last_error 文本外,另有两个只读
属性供上位系统分类告警——client.last_error_category(ErrorCategory
枚举:TRANSPORT/PROTOCOL/DEVICE/TIMEOUT/UNKNOWN)与
client.last_error_code(PLC 原始错误码,如 MC 结束码 0xC059、Modbus
异常码 2、FINS 结束码 0x2108;传输类错误为 errno,无码为 None)。
成功读写后三者一并清空::
from omniplc import ErrorCategory
ok, value = client.read_ushort("D100")
if not ok:
cat = client.last_error_category
if cat is ErrorCategory.DEVICE:
... # PLC 报错:看 last_error_code 区分地址越界/功能不支持
elif cat is ErrorCategory.TRANSPORT:
... # PLC 不可达:最高级告警
elif cat is ErrorCategory.TIMEOUT:
... # 链路完好但超时:查负载/串扰/超时配置
连接退避(v0.34.0 起)
连接失败(建连或握手)后自动进入指数退避门控:第 n 次连续失败后,
下一次 connect() 在 uniform(0, min(0.5 × 2ⁿ, 30)) 秒内被直接拒绝
(返回 False,last_error 提示"连接退避中")——时间戳比较,不发包、
不 sleep,PLC 断电/网线松动时紧密轮询的调用方不再形成高频重连风暴。
连接成功或显式 disconnect() 后门控与失败计数全部重置。
- 默认开启;
client.reconnect_backoff = False一行恢复 v0.33 行为 client.next_connect_in:距下次允许连接的剩余秒数(None= 可立即连)- 门控拒绝不计入
stats["error_count"](无真实网络动作) - 显式
disconnect()会重置门控与失败计数(视为干净起点); 失败重试循环中请勿"先 disconnect 再 connect",否则退避不生效 - 配置了
retries时,门控窗口内的重试直接结束(不空转),last_error保留武装门控的那次真实失败根因
连接健康统计(v0.30.0 起)
所有 BaseClient 子类提供 client.stats 只读快照,字段:
connect_count/disconnect_count/transactions/error_count/device_error_count:计数器(锁内更新)last_error_at/last_connect_at/last_success_at:time.monotonic()时间戳(秒);成功/失败/建连时刷新;跨重启无意义,用于现场判断 "多久没成功/多久前出错"last_rtt:最近一次成功事务的往返耗时(秒,含 PLC 等待),微秒级开销
异步镜像 ABaseClient.stats 同步转发。判断示例::
s = client.stats
if s["error_count"] > 10 and (s["last_success_at"] or 0) < (time.monotonic() - 60):
# 错误多且一分钟没成功过:报警/触发诊断
...
真机联测待做(v0.30.0 整理)
下表汇总散落各处的真机核证项(实现已完成,缺真机条件或排队中):
| 项 | 驱动 | 现状态 |
|---|---|---|
| AB 0x0A 多服务包批量读 | AB Logix | 已实现,HSL 模拟器不支持,待真机核证 |
| NJ CIP 0x0A 多服务包 | 欧姆龙 NJ/NX CIP | 继承 AB,理论同,待真机核证 |
| 倍福 ADS | TwinCAT | 封装 pyads,需 TwinCAT 运行时 |
| 西门子 S7 | S7-300/1200/1500 | 封装 python-snap7,需 PLC 或 PLCSIM |
| NJ STRING 拒绝 | 欧姆龙 NJ/NX CIP | 编码已禁,待真机复核边界 |
| KV MC 0406 批量读 | 基恩士 KV MC | 继承 MelsecMc,码表已覆写,待真机 |
| OPC-UA | opc.tcp | 封装 asyncua,需 OPC-UA 服务器 |
| MTConnect | MTConnect Agent | 标准库 HTTP/XML,需 CNC 端 Agent |
| MX Component | 三菱 MX | 读写/批量/CPU 型号/时钟已真机核证;get_error_message(ActSupportMsg)待核证 |
v1 协议 × 走线矩阵
| 协议 | TCP | UDP | RTU(串口) | MX Component |
|---|---|---|---|---|
| Modbus(含 FC22 掩码写) | ✅ | — | ✅(广播写) | — |
| 三菱 MC 3E/4E/1E | ✅ | ✅ | ✅(1C/3C/4C 串口帧) | ✅(Windows + COM) |
| 欧姆龙 FINS | ✅ | ✅ | v1.x(Host Link) | — |
| 欧姆龙 CIP / 连接型 CIP(NJ/NX) | ✅(44818,unconnected/connected) | — | — | — |
| 倍福 TwinCAT(ADS) | ✅(封装 pyads,AMS 851) | — | — | — |
| 罗克韦尔 AB EtherNet/IP(Logix) | ✅(44818,unconnected/connected) | — | — | — |
| 基恩士 KV Host Link | ✅ | ✅ | — | — |
| 基恩士 KV MC 协议兼容(SLMP 3E) | ✅(5000) | ✅(5000) | — | — |
| 汇川 H3U/H5U(Modbus + 汇川地址映射) | ✅(502) | — | ✅(9600-8N2) | — |
| 汇川 MC 协议兼容(3E 帧,Easy/H5U 固件 V6.4.0.0+) | ✅(端口以 MC配置 为准) | — | — | — |
| 松下 FP0H/FP7 MC 协议兼容(3E 帧) | ✅(端口以模块配置为准) | — | — | — |
| 松下 MEWTOCOL | ✅(1024) | ✅(1024) | v1.x(MEWTOCOL-COM) | — |
| 基恩士 SR 扫码枪 | ✅(9004) | — | — | — |
| 丰田 TOYOPUC 计算机链接 | ✅(1025) | ✅(1025) | — | — |
| OPC-UA(opc.tcp) | ✅(4840,封装 asyncua) | — | — | — |
| CNC 机床数采(MTConnect) | ✅(Agent 5000,HTTP/XML 只读) | — | — | — |
| 西门子 S7(DB/I/Q/M) | ✅(102,封装 python-snap7:3.7~3.9→1.3,3.10+→3.x 纯 Python) | — | — | — |
| 通用自定义 TCP(分隔符成帧) | ✅(分隔符/编码/帧上限可配) | — | — | — |
路线图
- v0.34.0(当前):可靠性 P0 双项——(1)连接退避门控:建连/握手失败后指数退避(full jitter,
uniform(0, min(0.5×2ⁿ, 30))秒,monotonic()时间戳门控零 sleep),reconnect_backoff可写属性(默认开,retries同款)+next_connect_in只读属性;防 PLC 断电高频重连风暴,门控拒绝不计error_count,连接成功/显式断开全重置;(2)失败结构化:ErrorCategory五值枚举(TRANSPORT/PROTOCOL/DEVICE/TIMEOUT/UNKNOWN,TIMEOUT 独立——链路完好可重试,与坏帧区分)+last_error_category/last_error_code只读属性(DeviceError.code协议原始码 /OSError.errno直通),last_error文本契约不变;失败写入统一收口_set_error/_clear_error(SR 扫码枪 ×5、AB 解码 ×2 直写点迁移);aio 镜像全量转发,内省守卫通过。决策与依据:docs/superpowers/specs/2026-09-22-v0.34-reliability-observability-design.md。 - v0.33.0:MC A 兼容 1C 帧落地(
MelsecMcSerialClient(frame="1C"),命令 BR/WR/BW/WW)——C24 A 兼容通信,ASCII 格式 4(ENQ 起始、和校验后接 CR LF),与 3C/4C 共享同一串口客户端;软元件规格 X/Y/B/W 十六进制、M/L/S/F/D/R 十进制,T/C 双性质映射(字=TN/CN、位读=TS/CS、位写=TC/CC),位软元件按字访问起始编号须 16 的倍数;点数上限 BR 256/BW 160/WR·WW 64 字(位软元件按字 32/10);消息等待(message_wait,0~15,10ms 单位)构造期可配;与 3C 的差异——无帧识别码、路由缩为站号+PC 号、错误代码 2 位、命令字 BR/BR/WW/WW(ACPU 共通),JR/QR/BT/WT/监视登录/扩展文件寄存器/TT 等与点位读写契约不符未做;以本地 SH-080008-AB 第 17 章逐字节核证(codec_serial_a.py),黄金向量入库(21 例含 T/C 映射、点位上下限、回显/和校验/控制码坏帧、aio 镜像);docs/architecture.mdMC 串口段补 1C 详解,协议覆盖表 RTU 列 3C/4C → 1C/3C/4C,backlog 行 1C/2C → 2C。点位表 schema 破坏性变更(随本期发布):Tag.name→tag_id(字母标识)+ 新增remark(中文备注,可选);迁移指南见 README「点位表」节与tag.py文档。 - v0.32.1:CI 修复——Release 发布物收紧为显式
dist/*.whl+dist/*.tar.gz:uv build会在dist/生成.gitignore(内容*,防构建产物污染 VCS,本地复现坐实),原dist/*通配把它一并挂上 Release(GitHub 存储名default.gitignore、显示.gitignore);收紧后发布物仅可能为 wheel + sdist,aio 产物清单同步收紧 - v0.32.0:API 对齐批——(1)FINS 节点号自动推导:
destination_node/source_node(TCP 另含local_node)缺省None自动获取——UDP 从 IP 末段推导(目标 = PLC IP 末段,源 = 本机出口 IP 末段,UDP connect 探测取同一路由出口),TCP 经握手获取;显式传值原样使用(OmronFins UDP 读 0x2108 真机排查的配套改进);(2)参数归位四分法成文(architecture.md §2.1:构造 = 身份冻结/可写属性 = 行为调优/configure_serial= 物理链路/只读 = 运行态;判定测试"改了它是不是等于换了一个对端")并补齐只读属性:FINS 路由六参、MCnetwork_number/pc_number、MC 串口self_station_number/module_station(含 aio 镜像,内省守卫通过);(3)双入口一律取消:Modbusstation与 SRscan_dwell属性转只读(校验移到构造期),全库不变量 = 构造参数一律冻结、可写属性一律不进构造(仅剩超时/重试/字序五件);(4)S7 构造签名对齐:(ip, rack, slot, port, dll_path)→(ip, port, rack, slot, dll_path)(破坏性:位置参数调用需调整,关键字调用不受影响) - v0.31.4:CI 修复批——
uv build显式指定--python 3.12:仓库.python-version钉 3.7.9(本地开发用),GitHub runner 无此解释器且 uv 托管下载不支持 3.7(首跑报 "No interpreter found for Python 3.7.9");构建本身与运行版本无关(hatchling 纯 Python,产物 wheel 为py3-none-any),setup-uv 预装 3.12 后构建恢复正常 - v0.31.3:CI 发布流水线——新增 GitHub Actions 工作流(
.github/workflows/build-release.yml),打标签推送(v*)自动触发:uv build构建 sdist + wheel、挂到对应 GitHub Release,并经**可信发布(OIDC)**发布到 PyPI(工作流内无明文令牌;fork守卫防误发;Gitee Go 流水线为企业付费服务,个人账号不可用故未配置) - v0.31.2:文档完善批——(1)全库客户端类构造参数注释补全:AST 审计 43 处缺口全部补齐(aio 异步包装 29 处"参数同 X"引用式改为显式
:param、同步客户端 9 处委托式改为显式参数块——OmronFinsTcpClient补漏注的 6 个 FINS 路由字段、内部会话适配器 5 处补全),措辞与同步侧逐字一致防漂移;(2)README 同步修正:类继承图补MTConnectClient/SiemensS7Client节点与箭头、异步镜像清单补AMTConnectClient/ASiemensS7Client、安装节补[ads]/[s7]extra;(3)使用范例修正:read_many返回逐点列表的语义错误解包修复(ok, values = ...改为results = ...),全部 7 个代码块经 AST+真实签名审计零问题;(4)路线图按版本号降序重排(新版本在前) - v0.31.1:MX Component 块读写原始 vtable 调用补第 4 参——
ReadDeviceBlock/WriteDeviceBlock/ReadDeviceRandom/WriteDeviceRandom的 raw 调用须按 vtable 全参收,尾参lplRetCode(通信函数的返回值,[out,retval])也要自备ctypes.c_long缓冲传入(3 参调用真机报 "this function takes 4 arguments (3 given)",实机两轮联测核证);方法返回值为 COM 层 HRESULT,业务出错码以尾缓冲内容为准,新增_check_com_call双校验(HRESULT 失败 = COM 层问题,通信函数返回值非 0 = PLC 出错代码);this由 comtypes 绑定自动提供、不占参数位——报错文案判别:raw 通道 "this function takes N (M given)" 的 N 是业务参数个数、高层包装 "call takes exactly N" 的 N 含 self;get_cpu_type/get_clock高层双形态路径实机确认正常 - v0.31.0:MX Component 真机联测批(真机两轮联测核证 COM 出参口径 + 查询能力扩充)——(1)comtypes 出参约定逐方法修正:
GetDevice出参收进返回值,单参调用直接返回数据(传 byref 缓冲报参数个数 TypeError);ReadDeviceBlock/ReadDeviceRandom数组参数以 ctypes 缓冲传入原地填充(省略缓冲调用不报错但返回值是出错代码、数据被丢弃,症状int object is not iterable),返回码照常校验;SetDevice/WriteDeviceBlock纯入参;GetDevice 路径失败以 COMError 形态出现,翻译为OmniPLCInternalError走断线重连;(2)新增方法:write_batch随机批量写(WriteDeviceRandom;位 + 16 位字混合单事务,拒绝位后缀挂字软元件、32 位类型、超MX_MAX_BLOCK_WORDS)、get_cpu_type(GetCpuType)、get_clock/set_clock(Get·SetClockData,字段序 年/月/日/星期/时/分/秒)、get_error_message(GetErrorMessage,独立 ActSupportMsg 控件,ProgID 按命名惯例推定待真机核证);未核证方法防御性双形态(短参优先,TypeError 回退 byref VARIANT);均含 aio 异步镜像;(3)类型化读→MX 方法映射:GetDevice单点=1 字 16 位,read_int/uint/float走 ReadDeviceBlock 2 字(低字+高字×65536 小端拼合,INT 再转补码)、read_long/ulong/double走 4 字、read_string走 ⌈n/2⌉ 字拼字节;"2" 系列是 SHORT 型数据版而非 32 位 - v0.30.2:安全修复(2026-09-23 三路安全审计收口)——(1)网络长度域 recv 前上限快失败:FINS/TCP 32 位长度域 ≤8192(握手与数据路径共用)、MC 3E/4E 应答数据长 ≤8192、MC 4C 串口长度域 ≤4096、TOYOPUC 帧长 ≤2048、AB ENIP 长度域 ≤8192,超限按坏帧断线——恶意设备/中间人声明的超长收包不再能导致内存耗尽或事务阻塞(原 FINS
0xFFFFFFFF长度域可灌 350MB+);(2)逐字节收包改整事务 deadline:MC 4C / KV Host Link / SR 扫码枪 / OpenTcp 的收包循环一次性起算 deadline、每轮下发剩余预算,滴流对端不再能逐字节重置超时长占事务锁(原 4C 最长 ~54 小时、KV ~3.4 小时),各协议超时/断线语义不变;(3)MTConnect 响应体上限:分块读取 + 16MB 上限(超限坏帧断线),数值文本 >64 字符快拒(py3.11 前int()/float()对超长数字串超线性);补 9 例安全回归测试 - v0.30.1:修复合集——(1)MTConnect keep-alive 失效原位重建透明重试(Agent 空闲超时静默关连接后,下一次 GET 原位重建重试一次,轮询不再周期性断线;
urllib已裁决不采用:urllib.request显式Connection: close每请求新建 TCP,轮询场景是退步);(2)点位表write_tag整数点位逆缩放还原 int(真除法恒产生 float,被底层整数校验拒收,整数点位写入必挂——默认 scale=1.0 也挂),scale=0建表期拒绝,JSON 元素非对象/CSV 短行等导入异常契约收口;(3)OpenTcp 流式成帧改用recv_some(修复真服务端短帧永远收不到——TcpTransport.recv的"读满恰好 size"契约被当流式读误用,短帧凑不满卡到超时);(4)全协议收包语义收口:MC 1E 读错误响应按结束码分流(1E 帧无长度域,错误响应只有 2 字节头,原实现按成功长度继续收数据,阻塞到超时误判断线)、SR 扫码枪超时残留尽力清理防串帧(读超时不断线语义保留)、KV UDP 整包上限 2048→4096 对齐流式上限;测试基建新增 size 感知假 socket(scripted.ChunkSocket+mount_real_tcp),MC 1E/3E、Modbus、AB 真传输凑满语义回归 - v0.30.0:工业场景可靠性与观测诊断收口——(A1)
receive_timeoutsetter 下发到 live socket(TCP/UDP/串口);(A2)整事务 deadline 防涓流拖死(TCP/串口按monotonic()绝对 deadline 重设settimeout);(A3)新增TransportTimeoutError(DeviceError),串口/UDP 超时按链路完好不断线,TCP 仍 OSError 拆连防串帧;(A4)connect()/_after_connect()异常统一清理到干净状态(失败必 close+null+返回 False);(A5)AB connected 模式 CIP 状态 0x01(Connection failure)映射为ProtocolFrameError,断开惰性重连重建 Forward Open;其余 CIP 状态(只读等)保持 DeviceError 不拆连;(A6)TCP 默认启用 SO_KEEPALIVE,LinuxTCP_KEEPIDLE/INTVL/CNT(30s/5s/3 次),WindowsSIO_KEEPALIVE_VALS,best-effort 静默降级;(A7)aioclose()幂等——先尽力断开同步客户端再 shutdown executor,关闭后协议调用抛RuntimeError;(A8)UDP datagram 上限 2048→8192(MC_MAX_DATAGRAM/FINS_MAX_DATAGRAM);(A9)FINS/TCP 重连刷新自动节点号(构造期auto_*标志保留,握手结果在自动模式下每次覆盖);(A10)MX COMClose()先于_com清空,失败也清引用,CoUninitialize配对释放线程计数;(B1)BaseClient.stats健康统计(connect/disconnect/transactions/error/device_error 计数 + last_error_at/last_connect_at/last_success_at/last_rtt 时间戳),aio 镜像转发,README 字段说明 + 真机联测待做汇总清单 - v0.29.2:使用范例完善——逐客户端补读写与驱动特有扩展示例(MC/FINS/KV HostLink/MX 补完整读写,MX 补 ReadDeviceRandom 批量,AB 补 0x0A 批量与 Identity 服务),新增"批量读取(协议原生,单事务)"专节(五家原生能力与统一契约),异步节补多设备并发示例与性能提示(并发 ≈ 顺序 1/N 耗时)
- v0.29.1:异步镜像完整性收口——全量内省审计补齐 9 处缺口(基恩士 MC ×2/汇川 MC/松下 MC 的
read_batch经对称继承获得,AB/NJ 补generic_message/list_identity/get_plc_info/get_attribute_all/get_attribute_list,NJ 补slot,汇川 TCP/RTU 补station/word_order/write_mask_register);aio 家族镜像改对称继承结构(Keyence/Inovance/Panasonic MC 继承 AMelsecMc*,汇川 Modbus 继承 AModbusBaseClient);新增内省守卫测试:同步扩展必须有异步镜像,防再漂移 - v0.29:MX Component 批量读取——ActUtlType 原生
ReadDeviceRandom(read_batch混软元件单事务、read_many覆写;软元件列表换行分隔每条 1 字;仅 16 位类型 BOOL/SHORT/USHORT,32/64 位因地址编号原文透传无法安全拆字,逐点读取;手册 5.2.5 核证) - v0.28:CIP 与 OPC-UA 批量读取——AB 0x0A 多服务包(
read_batch混标签混类型单事务,BOOL 首次类型发现后入包,NJ/NX CIP 零改动继承;pylogix/cm_ethernetip 双参考核证,内嵌服务 32 条上限;HSL 模拟器不支持 0x0A,真机核证待做)+ OPC-UA UA Read 服务原生多节点(asyncuaread_values单请求,read_many覆写) - v0.27:欧姆龙 FINS 批量读取,利用协议原生 0104 多存储区读(
read_many覆写为单事务、read_batch混类型混软元件;W342 §5-3-5 核证:每条读 1 字、响应逐条区码回显、仅字码,以太网上限 167 条;BOOL 走包含字提位,T/C 完成标志不支持) - v0.26:MC 3E/4E 批量读取,利用协议原生 0406 多块批量读(
read_many覆写为单事务、read_batch混类型混软元件;SH-080008 §8.4 逐字节核证,总块数 ≤120;汇川/基恩士 MC 兼容子类经地址换算钩子继承;1E/3C/4C 回退逐点/拒绝) - v0.25:OpenTcpClient 补定长成帧(
frame_length,二进制固定帧设备;与delimiter互斥、构造期二选一校验,frame_length≤max_frame,append_delimiter强制关;跨分片/多帧/残字节语义与分隔符模式一致,异步镜像同步) - v0.24.1:S7 依赖按 Python 版本自动二选一(3.7~3.9 → python-snap7 1.3,extra 带 setuptools 修 pkg_resources;3.10+ → 3.x 纯 Python 无需 DLL),并修复区码需转 snap7
Areas枚举的兼容问题(1.x 裸 int 读抛 ValueError/写抛 AttributeError);错误边界适配 3.xS7Error谱系 - v0.24:西门子 S7(封装 python-snap7,rack/slot 路由 102;DB/I/Q/M 绝对寻址,尺寸由 DataType 决定大端序,位读改写,S7 String;64 位 Python 用捆绑 snap7 库,32 位经 dll_path 自备)
- v0.23:CNC 机床数采 MTConnect(标准库 HTTP/XML 只读,Agent 默认 5000;数据项 id 即地址,类型化读 + 全量快照 + 报警条件项 + 设备信息;FANUC/三菱等控制器均可经 Agent 采集,零第三方依赖)
- v0.22:内部性能与整洁度优化(全驱动地址解析 lru_cache 缓存,MC 事务提速约 12%;会话型调试日志惰性格式化;check_byte_field 归位 core/validation)
- v0.21:全局报文调试开关(omniplc.set_debug;走线型在传输层统一输出请求/响应十六进制,会话型 OPC-UA/ADS/MX 输出操作级日志;logging 记录器 omniplc.debug,无日志配置时自动落 stderr)
- v0.20:倍福 TwinCAT ADS(封装 pyads 3.5.1,AMS 端口 851;变量名读写,DataType→PLCTYPE 映射,ADSError→DeviceError 不断线;pyads 无 py37 语法障碍,Windows 需 TcAdsDll)
- v0.19:通用自定义 TCP 客户端(OpenTcpClient;分隔符成帧 + 内部缓冲,重连/超时沿用 BaseClient 属性,receive/transact 支持 per-call timeout;超时不断线,坏帧/解码失败断线惰性重连,重连清空接收缓冲)
- v0.18:欧姆龙 CIP / 连接型 CIP(NJ/NX 内置 EtherNet/IP,44818;继承 AB 客户端,unconnected 直发无背板路由 + connected 连接路径只剩消息路由对象,NJ 变量读写;pycomm3 1.2.16 交叉核证)
- v0.17:AB connected CIP 消息(Forward Open 大/普通回落 + SendUnitData 序列号回显校验 + Forward Close,connected_messaging 参数启用)
- v0.16:罗克韦尔 AB EtherNet/IP(CIP,44818;Logix 标签读写,unconnected 消息 + 槽号路由,标签自描述,位/BOOL 数组原子读-改-写,STRING 结构体)
- v0.15:基恩士 KV MC 协议兼容 UDP 走线(继承 MelsecMcUdpClient,与 TCP 版共用码表覆写,端口 5000)
- v0.14:三菱 MC 串口帧(C24;3C 帧 ASCII 格式 4 / 4C 帧二进制格式 5,帧格式按 SH-080008 Appendix 7 逐字节核证)
- v0.13:松下 FP0H/FP7 MC 协议兼容(3E 帧)+ MEWTOCOL(TCP/UDP,1024;RCS/WCS/RD/WD,BCC 校验)
- v0.12:汇川 MC 协议兼容(3E 帧,继承 MelsecMcTcpClient;S→L 码、R=D+8000 统一编址、X/Y 八进制换算)
- v0.11:汇川 H3U/H5U(Modbus TCP/RTU,继承 Modbus 客户端换汇川地址映射)
- v0.10:基恩士 KV MC 协议兼容(SLMP 3E 帧,继承 MelsecMcTcpClient 换码表)
- v0.9:Modbus 协议对照强化(pymodbus 3.15 对照:RTU 广播写、FC22 掩码写、MBAP 长度上限)
- v0.8:OPC-UA opc.tcp 会话(封装 asyncua 1.1.5,NodeId 读写)
- v0.7:丰田 TOYOPUC 计算机链接(TCP/UDP,基础区字/字节/位访问)
- v0.6:基恩士 SR 扫码枪(LON/LOFF 触发扫码,bank 预设)
- v0.5:基恩士 KV Host Link(TCP/UDP,RD/RDS/WR/WRS)
- v0.4:三菱 MX Component(comtypes,逻辑站号)+ MC float 解码修正
- v0.3:三菱 MC 3E/4E/1E(TCP/UDP)+ 欧姆龙 FINS TCP/UDP(握手/节点分配)
- v0.2:Modbus TCP/RTU 编解码 + 黄金报文样本 + 脚本化链路测试
- v0.1:架构落地 + 公共层/传输层完整实现 + 协议客户端骨架 + 100 例测试
开发
uv sync --extra dev --extra mx # 安装开发依赖(Windows 加装 comtypes 供静态检查解析)
uv run python -m pytest # 测试(无需真机;32 位 py3.7 venv 的 exe shim 兼容性问题走 -m)
uv run ruff check . # 代码检查
uv run mypy # 类型检查(python_version = 3.7,配置见 pyproject.toml)
uvx ty check # ty 类型检查(Astral,配置见 pyproject.toml [tool.ty])
License
Release files for omniplc 0.34.0
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | |
|---|---|---|---|
| omniplc-0.34.0.tar.gz | 461.1 kB | Details |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Reset |
|---|---|---|---|---|
| omniplc-0.34.0-py3-none-any.whl | Python 3 | none | any | Details |
Total release size: 710.2 kB
Release files / omniplc-0.34.0.tar.gz
| Download URL | omniplc-0.34.0.tar.gz |
|---|---|
| Size | 461.1 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
d2332f443d606a3106783e2192e5c492c13f795514f89e2fafb394a858bf4d61
|
|
BLAKE2b-256 checksum How to use checksums |
a0f24344b805239fc4476e506cc2879237c0101dbcf415a9a53285f758bc449c
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.14
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Sep 24, 2026.
Transparency logRelease files / omniplc-0.34.0-py3-none-any.whl
| Download URL | omniplc-0.34.0-py3-none-any.whl |
|---|---|
| Size | 249.2 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
11d87bd8fef5ec83ad8a2adfc786562d02a6c40396ac34db4f82ccf6fff846bf
|
|
BLAKE2b-256 checksum How to use checksums |
ad977bdd26a37b3402de502e56ff3280f9020915bece5f9119ade13d0e232dc4
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.14
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Sep 24, 2026.
Transparency log