随笔 - 2  文章 - 0  评论 - 0  阅读 - 110

欧姆龙CIP协议-Python client上位机

参考文档

  1. 协议格式唯一真神CIP UCMM - OSD OMRON Sample
  2. 状态码含义查询c#基于TCP/IP、CIP协议的欧姆龙PLC通信 - 非凡AFK - 博客园

流程

  1. 通过sysmac studio软件,通过以太网连接欧姆龙PLC,查看PLC的IP。端口默认为44818。

  2. 阅读上方的协议文档,确定协议格式。

  3. 使用Python进行测试。这里简单写了一个注册-读取数据-注销的Python代码。在调试过程中,结合上文的状态码进行调试。

    import socket
    import struct
    
    
    class EtherNetIPClient:
        def __init__(self, host='192.168.1.5', port=44818):
            self.host = host
            self.port = port
            self.handle = None
            # self.client_socket = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
            self.client_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
    
        def connect(self):
            self.client_socket.connect((self.host, self.port))
            print(f"成功连接到 {self.host}:{self.port}")
    
        def register(self):
            # 注册报文
            register_message = bytes.fromhex(
                '65 00 04 00 00 00 00 00 00 00 00 00 ff ab cd ff ff dc ba ff 00 00 00 00 01 00 00 00'
            )
    
            # 发送注册请求
            self.client_socket.sendall(register_message)
            print("已发送注册请求:", register_message.hex())
    
            # 接收注册响应
            response = self.client_socket.recv(1024)
            print("接收到的注册响应:", response.hex())
    
            # 提取句柄
            if len(response) >= 8:
                handle = response[4:8].hex()  # 句柄为第5到第8字节
                print("保存的句柄:", handle)
                self.handle =  handle
                return handle  # 返回句柄
            return None
        def unregister(self):
            unregister_message = bytes.fromhex(
                '66 00 00 00 57 01 56 00 00 00 00 00 ff ab cd ff ff dc ba ff 00 00 00 00'
            )
            unregister_message = bytearray(unregister_message)
            # 将句柄插入到读取请求中
            unregister_message[4:8] = bytes.fromhex(self.handle)
            # 发送注册请求
            self.client_socket.sendall(unregister_message)
            print("已发送注销请求:", unregister_message.hex())
            pass
          
        def close(self):
            self.client_socket.close()
            print("client连接已关闭")
        def parse_response(self,response):
        # 确保响应至少有 4 个字节来读取长度字段
            if len(response) < 4:
                raise ValueError("Response is too short to even contain the length.")
      
            # 第一步:读取前 4 个字节,获取总长度字段
            header_length = struct.unpack('<H', response[2:4])[0]  # 假设长度是 2 字节
      
            # 检查是否接收到了足够的数据
            if len(response) < header_length:
                raise ValueError(f"Response length {len(response)} is shorter than the expected total length: {header_length}.")
      
            # 第二步:读取剩下的 header,header 长度为 header_length
            rest_header_length = header_length - 4  # header_length 是总长度减去前面已经读取的 4 个字节
            header = response[4:4 + rest_header_length]  # 读取 header 数据
      
            # 第三步:读取剩余的 Command Specific Data 部分
            command_specific_data = response[header_length:]  # 剩下的数据都是 command specific data
      
            # 现在我们需要解析 header 和 command specific data
            # 假设 header 的格式为 <HHII> (这是个示例)
            if len(header) >= 12:
                command, session_handle, status, options = struct.unpack('<HHII', header[:12])
            else:
                raise ValueError("Header is too short to extract all fields.")
      
            # 将结果组织为字典
            parsed_data = {
                'Total Length': header_length,
                'Header Length': rest_header_length,
                'Command': command,
                'Session Handle': session_handle,
                'Status': status,
                'Options': options,
                'Command Specific Data': command_specific_data
            }
      
            return parsed_data
    
    
        def read_message(self, tag):
            # 封装读取报文,使用句柄和标签
            header_bytes = bytearray([
                # 前24字节是header
                0x6F, 0x00, # A 读的报头,固定
                0x18,0x00,  # B command specific data + cip报文的长度,从G到S的长度(字节)
                0x70, 0x01, 0x27, 0x00,  # C 会话句柄
                0x00, 0x00, 0x00, 0x00,  # D 状态,固定
                0xff, 0xab, 0xcd, 0xff,0xff, 0xdc, 0xba, 0xff,  # E 发送方描述,固定
                0x00, 0x00, 0x00, 0x00,  # F 选项,固定
            ])
            command_specific_data_bytes = bytearray([
                # 以下为 Command Specific Data
                0x00,0x00,0x00,0x00,  # G 接口句柄,固定
                0x00, 0x00,  # H 超时,固定
                0x02, 0x00,  # I 项数,固定
                0x00, 0x00,  # J 空地址项,固定
                0x00, 0x00,  # K 空地址项长度,固定
                0xB2, 0x00,  # L 未连接项,固定
                0x08, 0x00,  # M 后面CIP报文包的长度
            ])
            CIP_bytes = bytearray([
                # 以下是CIP报文
                0x4C,  # N 服务标识,固定
                0x00,  # O 从P到R字长度(1字是4字节,四个0x00)
                0x91,  # P 扩展符号,固定
                0x01,  # Q 数据大小,就是tag字符串转byte的长度(字)
                # 0x00, 0x00,  # R 数据名称,就是tag字符串,长度为Q
                # 0x01, 0x00,  # S 服务命令指定数据,固定
          
            ])
    
            # 将句柄插入到读取请求中
            header_bytes[4:8] = bytes.fromhex(self.handle) 
    
            # 插入标签字节
          
            # len(hex_tag) 是字节长度的 2 倍,除以 2 得到字节数
            hex_tag = tag.encode('UTF-8').hex()  # 将标签字符串转换为16进制
            # 检查标签长度是否为奇数
            if len(tag) % 2 != 0:
                hex_tag += '00'  # 补充 '00'
          
            # 以alarm1为例,现在hex_tag为0791616c61726d31,length_hex为04(字)
            length_hex = '{:02x}'.format(len(hex_tag) // 4+1)
          
            # 将字符串长度添加到 request_message
          
            # 插入长度部分(必须是两位十六进制)
            CIP_bytes[1:2]= bytes.fromhex(length_hex)
            CIP_bytes[3:4]= bytes.fromhex('{:02x}'.format(len(hex_tag) // 2 if len(tag)% 2 ==0 else len(hex_tag) // 2 -1)) # tag的长度
            # 插入 hex_tag 本身
            CIP_bytes += bytes.fromhex(hex_tag)
            # 插入固定的服务命令指定数据
            CIP_bytes += bytes.fromhex('0100')
            # 计算CIP报文长度
            # CIP_length = len(hex_tag) // 2 + 4
            CIP_length_hex = struct.pack('<H', len(CIP_bytes)).hex()
            # print("CIP_length_hex:", CIP_length_hex)
            command_specific_data_bytes[14:16] = bytes.fromhex(CIP_length_hex)
            # 计算command specific data的长度
            command_specific_data_length = len(command_specific_data_bytes)+len(CIP_bytes)
            command_specific_data_length_hex = struct.pack('<H', command_specific_data_length).hex()
            # print("command_specific_data_length_hex:", command_specific_data_length_hex)
            header_bytes[2:4] = bytes.fromhex(command_specific_data_length_hex)
            print("header_bytes:", header_bytes.hex())
            print("command_specific_data_bytes:", command_specific_data_bytes.hex())
            print("CIP_bytes:", CIP_bytes.hex())
            request_message = header_bytes + command_specific_data_bytes + CIP_bytes
            # 发送读取请求
            self.client_socket.sendall(request_message)
            print("已发送读取请求:", request_message.hex())
    
            # 接收响应
            response = self.client_socket.recv(1024)
            print("接收到的响应:", response.hex())
            # print(self.parse_response(response))
            # # 解析返回报文
            # command_code = response[0:2].hex()
            # length = response[2:4].hex()
            # session_handle = response[4:8].hex()
            # session_status = response[8:12].hex()
            # item_count = response[16:18].hex()
            # data_type = response[-4:-2].hex()  # 数据类型
            # data_value = response[-2:].hex()    # 数据值
    
            # print(f"命令码: {command_code}")
            # print(f"长度: {length}")
            # print(f"会话句柄: {session_handle}")
            # print(f"会话状态: {session_status}")
            # print(f"项数: {item_count}")
            # print(f"数据类型: {data_type}")
            # print(f"数据值: {data_value}")
    
    
    if __name__ == "__main__":
        client = EtherNetIPClient()
        client.connect()        # 先连接
        client.register()  # 注册
        if client.handle:
            # tag_name = "PB_ALARM_RST"  # ok
            tag_name = "IND_MODE"  # ok
            # tag_name = "AL.B[1]"  # 指定标签名称
            client.read_message( tag_name)  # 使用句柄和标签读取数据
        client.unregister()
        client.close()          # 最后关闭连接
    
    
posted on   xep4ZE  阅读(58)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 阿里巴巴 QwQ-32B真的超越了 DeepSeek R-1吗?
· 10年+ .NET Coder 心语 ── 封装的思维:从隐藏、稳定开始理解其本质意义
· 【译】Visual Studio 中新的强大生产力特性
· 【设计模式】告别冗长if-else语句:使用策略模式优化代码结构
· 字符编码:从基础到乱码解决
< 2025年3月 >
23 24 25 26 27 28 1
2 3 4 5 6 7 8
9 10 11 12 13 14 15
16 17 18 19 20 21 22
23 24 25 26 27 28 29
30 31 1 2 3 4 5

点击右上角即可分享
微信分享提示