You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何通过Lua Sockets实现Ethernet/IP上的显式消息通信?

使用Lua Sockets实现Ethernet/IP Class 3显式消息通信(Lua 5.1)

核心前提:明确Ethernet/IP显式消息结构

Ethernet/IP的Class 3显式消息基于TCP协议,核心是按照CIP(通用工业协议)规范打包请求/响应帧,主要包含:

  • TCP传输层(Lua Sockets自动处理)
  • CIP封装层头部
  • CIP路由路径
  • CIP服务请求码
  • 业务数据段

第一步:搭建Lua Sockets基础环境

先确保Lua 5.1环境已安装Lua Sockets模块,脚本中引入并建立TCP连接(Ethernet/IP默认端口为44818):

local socket = require("socket")

-- 建立TCP连接到伺服驱动器
local tcp = socket.tcp()
tcp:settimeout(1000) -- 设置1秒超时,按需调整
local ok, err = tcp:connect("192.168.1.100", 44818) -- 替换为你的设备IP
if not ok then
    print("连接失败:" .. err)
    return
end

第二步:构造CIP封装层头部

封装层是Ethernet/IP的外层包装,必须严格遵循规范,核心字段包括命令码、数据长度、会话句柄等:

-- 构造Send RR Data(请求响应数据)类型的封装头部
local encaps_header = string.char(0x00, 0x00) -- 命令码:Send RR Data
encaps_header = encaps_header .. string.char(0x00, 0x00) -- 数据长度(后续计算后替换)
encaps_header = encaps_header .. string.char(0x00, 0x00, 0x00, 0x00) -- 会话句柄(初始设为0)
encaps_header = encaps_header .. string.char(0x00, 0x00, 0x00, 0x00) -- 状态码(请求时设为0)
encaps_header = encaps_header .. string.char(0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00) -- Sender Context(自定义,默认0)
encaps_header = encaps_header .. string.char(0x00, 0x00) -- 选项(默认0)

第三步:构造CIP请求数据

这部分是核心,必须根据伺服驱动器的官方手册确定以下参数:

  1. 服务码:比如读取单个属性用0x0E(Get Attribute Single)
  2. 类ID:伺服驱动器的目标数据类(比如驱动器状态类可能是0x20)
  3. 实例ID:类的实例编号(一般为0x01)
  4. 属性ID:要读取的具体数据项(比如位置反馈可能是0x47)
  5. 路由路径:设备的CIP访问路径(示例为0x20,0x04,0x24,0x01,需按设备手册调整)

将这些参数打包为二进制字符串:

local service_code = string.char(0x0E) -- Get Attribute Single服务
local class_id = string.char(0x20, 0x00) -- 替换为目标类ID
local instance_id = string.char(0x01, 0x00) -- 替换为实例ID
local attribute_id = string.char(0x47, 0x00) -- 替换为属性ID
local route_path = string.char(0x20, 0x04, 0x24, 0x01) -- 替换为设备路由路径

-- 拼接完整CIP请求数据
local cip_request = service_code .. class_id .. instance_id .. attribute_id .. route_path

第四步:发送完整请求帧

更新封装头部的长度字段,拼接完整帧后通过TCP发送:

-- 更新封装头部的长度字段
local cip_len = #cip_request
encaps_header = string.sub(encaps_header, 1, 2) .. string.char(math.floor(cip_len/256), cip_len%256) .. string.sub(encaps_header, 5)

-- 拼接完整请求帧并发送
local request_frame = encaps_header .. cip_request
local bytes_sent, err = tcp:send(request_frame)
if not bytes_sent then
    print("发送失败:" .. err)
    tcp:close()
    return
end

第五步:接收并解析响应

接收响应后先校验封装层状态,再解析CIP业务数据:

-- 接收封装层头部(固定24字节)
local resp_header, err = tcp:receive(24)
if not resp_header then
    print("接收头部失败:" .. err)
    tcp:close()
    return
end

-- 校验封装层状态码
local status = string.byte(resp_header, 8) + string.byte(resp_header,9)*256 + string.byte(resp_header,10)*65536 + string.byte(resp_header,11)*16777216
if status ~= 0 then
    print("请求错误,状态码:" .. string.format("0x%08X", status))
    tcp:close()
    return
end

-- 接收CIP响应数据
local resp_data_len = string.byte(resp_header,3)*256 + string.byte(resp_header,4)
local resp_data, err = tcp:receive(resp_data_len)
if not resp_data then
    print("接收数据失败:" .. err)
    tcp:close()
    return
end

-- 解析业务数据(示例:4字节整数位置反馈)
local resp_service = string.byte(resp_data,1)
if resp_service == 0x0E + 0x80 then -- 服务响应码为请求码+0x80表示成功
    local position = string.byte(resp_data,5) + string.byte(resp_data,6)*256 + string.byte(resp_data,7)*65536 + string.byte(resp_data,8)*16777216
    print("当前位置反馈:" .. position)
else
    print("服务响应错误:" .. string.format("0x%02X", resp_service))
end

第六步:实现轮询逻辑

将发送-接收逻辑放入循环,设置轮询间隔(注意保持TCP长连接以提升效率):

local poll_interval = 100 -- 100毫秒轮询一次
while true do
    -- 复用之前的TCP连接,重复执行发送、接收、解析逻辑
    -- ... 省略重复代码 ...
    socket.sleep(poll_interval/1000) -- sleep函数接收秒为单位
end

关键注意事项

  • 设备手册是核心:不同品牌伺服驱动器的类ID、属性ID等参数差异极大,必须严格参考设备的Ethernet/IP或CIP文档。
  • 二进制字节序:Ethernet/IP多数采用小端字节序,处理多字节数据时需注意顺序,可借助struct模块(Lua 5.1需额外安装)简化打包解包。
  • 错误处理:必须处理连接超时、发送失败、接收超时等异常,避免脚本崩溃。
  • 长连接优化:Class 3显式消息支持TCP长连接,不要每次轮询都重建连接。

内容的提问来源于stack exchange,提问作者Mike Cardoso

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.22 19:04:56