1. 程式人生 > >Python 實現Ethernet/IP 通訊

Python 實現Ethernet/IP 通訊

EtherNet / IP是為了在乙太網中使用CIP協議而進行的封裝.EtherNet / IP的CIP幀封裝了命令,資料點和訊息等資訊.CIP幀包括CIP裝置配置檔案資料包的其餘部分是乙太網/ IP幀,CIP幀通過它們在乙太網上傳輸。EIP一般使用TCP / UDP的44818埠執行,還有一個2222埠,這兩個埠分別實現隱式訊息傳遞和顯示訊息傳遞兩種方式。客戶端/伺服器訊息,而隱訊息為I / O訊息。

詳細資訊可參考下面文章

https://www.cnblogs.com/blacksunny/p/7202815.html

下面是Python 實現的程式碼,轉載自https://github.com/paperwork/pyenip/blob/605ad6d026865e3378542d4428ec975e7c26d2e4/ethernetip.py

 

"""
  Copyright (C) 2014 Sebastian Block
   
  This program is free software; you can redistribute it and/or modify
  it under the terms of the GNU General Public License as published by
  the Free Software Foundation; either version 2 of the License.
   
  This program is distributed in the hope that it will be useful,
  but WITHOUT ANY WARRANTY; without even the implied warranty of
  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  GNU General Public License for more details.
   
  You should have received a copy of the GNU General Public License along
  with this program; if not, write to the Free Software Foundation, Inc.,
  51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
  """
   
  import random
  import select
  import socket
  import struct
  import threading
  import time
   
  from .dpkt import dpkt
   
  ENIP_TCP_PORT = 44818
  ENIP_UDP_PORT = 2222
   
  #/* Common Services */
  CI_SRV_GET_ALL = 0x01
  CI_SRV_SET_ATTR_ALL = 0x02
  CI_SRV_GET_ATTR_LIST = 0x03
  CI_SRV_SET_ATTR_LIST = 0x04
  CI_SRV_RESET = 0x05
  CI_SRV_START = 0x06
  CI_SRV_STOP = 0x07
  CI_SRV_CREATE = 0x08
  CI_SRV_DELETE = 0x09
  CI_SRV_MULTIPLE_SRV = 0x0A
  CI_SRV_APPLY_ATTR = 0x0D
  CI_SRV_GET_ATTR_SINGLE = 0x0E
  CI_SRV_SET_ATTR_SINGLE = 0x10
  CI_SRV_FIND_NEXT_OBJ = 0x11
  CI_SRV_RESTORE = 0x15
  CI_SRV_SAVE = 0x16
  CI_SRV_NOP = 0x17
  CI_SRV_GET_MEMBER = 0x18
  CI_SRV_SET_MEMBER = 0x19
  CI_SRV_INSERT_MEMBER = 0x1A
  CI_SRV_REMOVE_MEMBER = 0x1B
  CI_SRV_GROUP_SYNC = 0x1C
  CI_SRV_FORWARD_CLOSE = 0x4E
  CI_SRV_UNCONN_SEND = 0x52
  CI_SRV_FORWARD_OPEN = 0x54
   
  #/* List of Objects */
  CIP_OBJ_IDENTITY = 0x01
  CIP_OBJ_MESSAGE_ROUTER = 0x02
  CIP_OBJ_ASSEMBLY = 0x04
  CIP_OBJ_CONNECTION = 0x05
  CIP_OBJ_CONNMANAGER = 0x06
  CIP_OBJ_DLR = 0x47
  CIP_OBJ_QOS = 0x48
  CIP_OBJ_BASE_SWITCH = 0x51
  CIP_OBJ_SNMP = 0x52
  CIP_OBJ_POWER_MANAGEM = 0x53
  CIP_OBJ_RSTP_BRIDGE = 0x54
  CIP_OBJ_RSTP_PORT = 0x55
  CIP_OBJ_PRP = 0x56
  CIP_OBJ_PRP_NODE_TABLE = 0x57
  CIP_OBJ_CONN_CONF = 0xF3
  CIP_OBJ_PORT = 0xF4
  CIP_OBJ_TCPIP = 0xF5
  CIP_OBJ_ETHERNET_LINK = 0xF6
   
  #/* The following are CIP (Ethernet/IP) Generic error codes */
  CIP_ROUTER_ERROR_SUCCESS = 0x00 # We done good...
  CIP_ROUTER_ERROR_FAILURE = 0x01 # Connection failure
  CIP_ROUTER_ERROR_NO_RESOURCE = 0x02 # Resource(s) unavailable
  CIP_ROUTER_ERROR_INVALID_PARAMETER_VALUE = 0x03 # Obj specific data bad
  CIP_ROUTER_ERROR_INVALID_SEG_TYPE = 0x04 # Invalid segment type in path
  CIP_ROUTER_ERROR_INVALID_DESTINATION = 0x05 # Invalid segment value in path
  CIP_ROUTER_ERROR_PARTIAL_DATA = 0x06 # Not all expected data sent
  CIP_ROUTER_ERROR_CONN_LOST = 0x07 # Messaging connection lost
  CIP_ROUTER_ERROR_BAD_SERVICE = 0x08 # Unimplemented service code
  CIP_ROUTER_ERROR_BAD_ATTR_DATA = 0x09 # Bad attribute data value
  CIP_ROUTER_ERROR_ATTR_LIST_ERROR = 0x0A # Get/set attr list failed
  CIP_ROUTER_ERROR_ALREADY_IN_REQUESTED_MODE = 0x0B # Obj already in requested mode
  CIP_ROUTER_ERROR_OBJECT_STATE_CONFLICT = 0x0C # Obj not in proper mode
  CIP_ROUTER_ERROR_OBJ_ALREADY_EXISTS = 0x0D # Object already created
  CIP_ROUTER_ERROR_ATTR_NOT_SETTABLE = 0x0E # Set of get only attr tried
  CIP_ROUTER_ERROR_PERMISSION_DENIED = 0x0F # Insufficient access permission
  CIP_ROUTER_ERROR_DEV_IN_WRONG_STATE = 0x10 # Device not in proper mode
  CIP_ROUTER_ERROR_REPLY_DATA_TOO_LARGE = 0x11 # Response packet too large
  CIP_ROUTER_ERROR_FRAGMENT_PRIMITIVE = 0x12 # Primitive value will fragment
  CIP_ROUTER_ERROR_NOT_ENOUGH_DATA = 0x13 # Goldilocks complaint #1
  CIP_ROUTER_ERROR_ATTR_NOT_SUPPORTED = 0x14 # Attribute is undefined
  CIP_ROUTER_ERROR_TOO_MUCH_DATA = 0x15 # Goldilocks complaint #2
  CIP_ROUTER_ERROR_OBJ_DOES_NOT_EXIST = 0x16 # Non-existant object specified
  CIP_ROUTER_ERROR_NO_FRAGMENTATION = 0x17 # Fragmentation not active
  CIP_ROUTER_ERROR_DATA_NOT_SAVED = 0x18 # Attr data not previously saved
  CIP_ROUTER_ERROR_DATA_WRITE_FAILURE = 0x19 # Attr data not saved this time
  CIP_ROUTER_ERROR_REQUEST_TOO_LARGE = 0x1A # Routing failure on request
  CIP_ROUTER_ERROR_RESPONSE_TOO_LARGE = 0x1B # Routing failure on response
  CIP_ROUTER_ERROR_MISSING_LIST_DATA = 0x1C # Attr data not found in list
  CIP_ROUTER_ERROR_INVALID_LIST_STATUS = 0x1D # Returned list of attr w/status
  CIP_ROUTER_ERROR_SERVICE_ERROR = 0x1E # Embedded service failed
  CIP_ROUTER_ERROR_VENDOR_SPECIFIC = 0x1F # Vendor specific error
  CIP_ROUTER_ERROR_INVALID_PARAMETER = 0x20 # Invalid parameter
  CIP_ROUTER_ERROR_WRITE_ONCE_FAILURE = 0x21 # Write once previously done
  CIP_ROUTER_ERROR_INVALID_REPLY = 0x22 # Invalid reply received
  CIP_ROUTER_ERROR_BAD_KEY_IN_PATH = 0x25 # Electronic key in path failed
  CIP_ROUTER_ERROR_BAD_PATH_SIZE = 0x26 # Invalid path size
  CIP_ROUTER_ERROR_UNEXPECTED_ATTR = 0x27 # Cannot set attr at this time
  CIP_ROUTER_ERROR_INVALID_MEMBER = 0x28 # Member ID in list nonexistant
  CIP_ROUTER_ERROR_MEMBER_NOT_SETTABLE = 0x29 # Cannot set value of member
  CIP_ROUTER_ERROR_UNKNOWN_MODBUS_ERROR = 0x2B # Unhandled Modbus Error
  CIP_ROUTER_ERROR_STILL_PROCESSING = 0xFF # Special marker to indicate we haven't finished processing the request yet
   
  # Extended status in Forward open response
  CIP_FWD_OPEN_EXTENDED_STATUS_INVALID_CONFIGURATION_SIZE = 0x126
   
  class EncapsulationPacket(dpkt.Packet):
  # commands
  ENCAP_CMD_NOP = 0x0000
  ENCAP_CMD_LISTSERVICES = 0x0004
  ENCAP_CMD_LISTIDENTITY = 0x0063
  ENCAP_CMD_LISTINTERFACES = 0x0064
  ENCAP_CMD_REGISTERSESSION = 0x0065
  ENCAP_CMD_UNREGISTERSESSION = 0x0066
  ENCAP_CMD_SENDRRDATA = 0x006F
  ENCAP_CMD_SENDUNITDATA = 0x0070
  ENCAP_CMD_INDICATESTATUS = 0x0072
  ENCAP_CMD_CANCEL = 0x0073
  # status
  ENCAP_STATUS_SUCCESS = 0x0000
  ENCAP_STATUS_INVALID_CMD = 0x0001
  ENCAP_STATUS_OUT_OF_MEMORY = 0x0002
  ENCAP_STATUS_INCORRECT_DATA = 0x0003
  ENCAP_STATUS_INVALID_LENGTH = 0x0065
  ENCAP_STATUS_UNSUPPORTED_VERSION = 0x0069
   
  __byte_order__ = '<'
  __hdr__ = (('command', 'H', 0),
  ('length', 'H', 0),
  ('session', 'I', 0),
  ('status', 'I', 0),
  ('sender_context', '8s', bytes([0,0,0,0,0,0,0,0])),
  ('options', 'I', 0))
   
  class CommandSpecificData(dpkt.Packet):
  # type ID
  TYPE_ID_NULL = 0x0000
  TYPE_ID_LIST_IDENT_RESPONSE = 0x000C
  TYPE_ID_CONNECTION_BASED = 0x00A1
  TYPE_ID_CONNECTED_TRANSPORT_PACKET = 0x00B1
  TYPE_ID_UNCONNECTED_MESSAGE = 0x00B2
  TYPE_ID_LISTSERVICES_RESPONSE = 0x0100
  TYPE_ID_SOCKADDR_INFO_ORIG_TARGET = 0x8000
  TYPE_ID_SOCKADDR_INFO_TARGET_ORIG = 0x8001
  TYPE_ID_SEQUENCED_ADDRESS = 0x8002
   
  __byte_order__ = '<'
  __hdr__ = (('item_count', 'H', 0),
  ('type_id', 'H', 0),
  ('length', 'H', 0))
   
  class UnconnectedDataItem(dpkt.Packet):
   
  UNCONN_DATA_ITEM_SERVICE_REQUEST = 0x00
  UNCONN_DATA_ITEM_SERVICE_RESPONSE = 0x80
   
  __byte_order__ = '<'
  __hdr__ = (('type_id', 'H', 0),
  ('length', 'H', 0),
  ('service', 'B', 0))
   
  class UnconnectedDataItemHdr(dpkt.Packet):
  __byte_order__ = '<'
  __hdr__ = (('type_id', 'H', 0),
  ('length', 'H', 0))
   
  class UnconnectedDataItemResp(dpkt.Packet):
  __byte_order__ = '<'
  __hdr__ = (('type_id', 'H', 0),
  ('length', 'H', 0),
  ('service', 'B', 0),
  ('resv', 'B', 0),
  ('status', 'B', 0),
  ('additional_status_size', 'B', 0))
   
  class ForwardOpenReq(dpkt.Packet):
  # network connection parameter bit offsets
  FORWARD_OPEN_CONN_PARAM_BIT_CONN_SIZE = 0
  FORWARD_OPEN_CONN_PARAM_BIT_FIXED_VAR = 9
  FORWARD_OPEN_CONN_PARAM_BIT_PRIORITY = 10
  FORWARD_OPEN_CONN_PARAM_BIT_CONN_TYPE = 13
  FORWARD_OPEN_CONN_PARAM_BIT_REDAN_OWN = 15
   
  __byte_order__ = '<'
  __hdr__ = (('mkpath', '5s', b"00000"), # len, 2 path
  ('prio_tick', 'B', 0x0A), # 4 Bit prio, 4 Bit tick time
  ('timeout_ticks', 'B', 0xF0),
  ('otconnid', 'I', 0xdeadbeaf),
  ('toconnid', 'I', 0xaffedead),
  ('conn_serial', 'H', 0x4949),
  ('vendor', 'H', 1),
  ('orig_serial', 'I', 0xbeeff00d),
  ('multiplier', 'B', 1),
  ('reserved', '3s', b"000"),
  ('otrpi', 'I', 0x186a0), # 100 ms
  ('otparams', 'H', 0x480c),
  ('torpi', 'I', 0x186A0), # 100 ms
  ('toparams', 'H', 0x2808),
  ('type_trigger', 'B', 0x01),
  ('plen', 'B', 9))
   
  class ForwardOpenResp(dpkt.Packet):
  __byte_order__ = '<'
  __hdr__ = (('reserved', '3s', ''),
  ('otconnid', 'I', 0),
  ('toconnid', 'I', 0),
  ('conn_serial', 'H', 0),
  ('vendor', 'H', 0),
  ('orig_serial', 'I', 0),
  ('otapi', 'I', 0),
  ('toapi', 'I', 0),
  ('appl_reply_size', 'B', 0),
  ('reserved2', 'B', 0)
  )
   
  class ForwardCloseReq(dpkt.Packet):
  __byte_order__ = '<'
  __hdr__ = (('mkpath', '5s', b"00000"), # len, 2 path
  ('prio_tick', 'B', 0x0A), # 4 Bit prio, 4 Bit tick time
  ('timeout_ticks', 'B', 0xF0),
  ('conn_serial', 'H', 0x4949),
  ('vendor', 'H', 1),
  ('orig_serial', 'I', 0xbeeff00d),
  ('plen', 'B', 9),
  ('reserved', 'B', 0)
  )
   
  class ForwardCloseResp(dpkt.Packet):
  __byte_order__ = '<'
  __hdr__ = (('reserved', '3s', ''),
  ('conn_serial', 'H', 0),
  ('vendor', 'H', 0),
  ('orig_serial', 'I', 0),
  ('appl_reply_size', 'B', 0),
  ('reserved2', 'B', 0)
  )
   
  class RegisterSessionPacket(dpkt.Packet):
  __byte_order__ = '<'
  __hdr__ = (('protocol_version', 'H', 1),
  ('option_flags', 'H', 0))
   
  class ListServicesReply(dpkt.Packet):
  # capcability flags
  CAP_CIP_ENCAP_VIA_TCP = 0x0020
  CAP_CIP_VIA_UDP = 0x0100
   
  __byte_order__ = '<'
  __hdr__ = (('version', 'H', 0),
  ('capability_flags', 'H', 0),
  ('name_of_service', '16s', ''))
   
  class ListIdentifyReply(dpkt.Packet):
  # status is a bit encoded word
  LIST_IDENT_STATUS_OWNED = 0x0001
  LIST_IDENT_STATUS_CONFIGURED = 0x0004
  LIST_IDENT_STATUS_EXTENDED_DEVICE_STATUS = 0x00F0
  LIST_IDENT_STATUS_MINOR_RECOVERABLE_FAULT = 0x0100
  LIST_IDENT_STATUS_MINOR_UNRECOVERABLE_FAULT = 0x0200
  LIST_IDENT_STATUS_MAJOR_RECOVERABLE_FAULT = 0x0400
  LIST_IDENT_STATUS_MAJOR_UNRECOVERABLE_FAULT = 0x0800
  LIST_IDENT_STATUS_EXTENDED_DEVICE_STATUS2 = 0xF000
  # states
  LIST_IDENT_STATE_NONEXISTENT = 0x00
  LIST_IDENT_STATE_SELF_TESTING = 0x01
  LIST_IDENT_STATE_STANDBY = 0x02
  LIST_IDENT_STATE_OPERATIONAL = 0x03
  LIST_IDENT_STATE_RECOVERABLE_FAULT = 0x04
  LIST_IDENT_STATE_UNRECOVERABLE_FAULT = 0x05
  LIST_IDENT_STATE_DEFAULT = 0xFF
   
  __byte_order__ = '<'
  __hdr__ = (('version', 'H', 1),
  ('socket_addr', '16s', ''),
  ('vendor_id', 'H', 0),
  ('device_type', 'H', 0),
  ('product_code', 'H', 0),
  ('revision_major', 'B', 0),
  ('revision_minor', 'B', 0),
  ('status', 'H', 0),
  ('serial_no', 'I', 0),
  ('product_name_length', 'B', 0),
  ('product_name', '0s', ''),
  ('state', 'B', 0))
   
  def unpack(self, buf):
  # product name can be a string upto 32 chars, but python does not
  # support variable string length, so we have to write a little
  # work-a-round and first unpack it check the length and calculate it
  # again
  tmp = dpkt.Packet.unpack(self, buf)
  self.__hdr_fmt__ = "<H16sHHHBBHIB" + str(self.product_name_length) + "sB"
  self.__hdr_len__ = struct.calcsize(self.__hdr_fmt__)
  dpkt.Packet.unpack(self, buf)
   
  class SendRRPacket(dpkt.Packet):
  __byte_order__ = '<'
  __hdr__ = (('interface_handle', 'I', 0),
  ('timeout', 'H', 10))
   
  class SocketAddressInfo(dpkt.Packet):
  __byte_order__ = '>' # big endian
  __hdr__ = (('sin_family', 'H', 0),
  ('sin_port', 'H', 0),
  ('sin_addr', 'I', 0),
  ('sin_zero', '8s', ''))
   
  class UdpSendDataPacket(dpkt.Packet):
  __byte_order__ = '<'
  __hdr__ = (('count', 'H', 2),
  ('type_id_seq_addr', 'H', 0x8002),
  ('len_seq_addr', 'H', 8),
  ('conn_id', 'I', 0),
  ('seq_num', 'I', 0),
  ('type_id_conn_data', 'H', 0x00b1),
  ('len_conn_data', 'H', 12),
  ('seq_count', 'H', 0))
   
  class UdpRecvDataPacket(dpkt.Packet):
  __byte_order__ = '<'
  __hdr__ = (('count', 'H', 2),
  ('type_id_seq_addr', 'H', 0x8002),
  ('len_seq_addr', 'H', 8),
  ('conn_id', 'I', 0),
  ('seq_num', 'I', 0),
  ('type_id_conn_data', 'H', 0x00b1),
  ('length', 'H', 12),
  ('unknown', 'H', 0)) # TODO check for what the first two bytes of data are
   
  class EthernetIOThread(threading.Thread):
  def __init__(self, typ, enip=None, conn=None):
  self.typ = typ
  self.enip = enip
  self.conn = conn
  threading.Thread.__init__(self)
   
  def run(self):
  if self.typ == 1:
  self.enip.listenUDP()
  elif self.typ == 2:
  self.conn.prodThread()
   
  class EtherNetIP(object):
  ENIP_IO_TYPE_INPUT = 0
  ENIP_IO_TYPE_OUTPUT = 1
   
  def __init__(self, ip="127.0.0.1"):
  self.assembly = {}
  self.explicit = []
  self.udpsock = None
  self.udpthread = None
  self.io_state = 0
  self.ip = ip
   
  def registerAssembly(self, iotype, size, inst, conn):
  if inst in self.assembly:
  print("Reg assembly failed for iotype=", iotype)
  return None
  bits = []
  for i in range(size*8):
  bits.append(0)
  self.assembly[inst] = (conn, iotype, bits)
  if conn != None:
  if iotype == EtherNetIP.ENIP_IO_TYPE_INPUT:
  conn.mapIn(bits)
  elif iotype == EtherNetIP.ENIP_IO_TYPE_OUTPUT:
  conn.mapOut(bits)
  return bits
   
  def startIO(self):
  if self.io_state == 0:
  self.udpsock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
  self.udpsock.bind(("0.0.0.0", ENIP_UDP_PORT))
  self.udpthread = EthernetIOThread(1,self)
  self.io_state = 1
  self.udpthread.start()
   
  def stopIO(self):
  if self.io_state == 1:
  self.io_state = 0
  self.udpsock.close()
   
  def listenUDP(self):
  while 1 == self.io_state:
  inp, out, err = select.select([self.udpsock], [], [], 2)
  if len(inp) != 0:
  try:
  buf, addr = self.udpsock.recvfrom(1024)
  except OSError:
  # If we close the socket asynchronously, the recv will
  # fail
  if self.io_state == 0:
  return
  raise
   
  addr = addr[0]
  pkt = UdpRecvDataPacket(buf)
   
  for inst in self.assembly:
  conn = self.assembly[inst][0]
  iotype = self.assembly[inst][1]
  bits = self.assembly[inst][2]
  # update i/o
  if conn.ipaddr == addr and iotype == EtherNetIP.ENIP_IO_TYPE_INPUT and pkt.conn_id == conn.toconnid:
  i = 0
  for byte in pkt.data:
  for s in range(8):
  if byte & (1 << s):
  bits[i] = True
  else:
  bits[i] = False
  i += 1
   
  def explicit_conn(self, ipaddr=None):
  if ipaddr is None:
  ipaddr = self.ip
  exp = EtherNetIPExpConnection(ipaddr)
  self.explicit.append(exp)
  return exp
   
  def listIDUDP(self, ipaddr=None, timeout=5):
  udpsock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
  udpsock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
  context = random.randint(1, 4026531839)
  pkt = EncapsulationPacket(command=EncapsulationPacket.ENCAP_CMD_LISTIDENTITY,
  sender_context=context.to_bytes(8, byteorder='big'))
  if ipaddr is None:
  ipaddr = self.ip
  udpsock.sendto(pkt.pack(),(ipaddr, ENIP_TCP_PORT))
  inp, out, err = select.select([udpsock], [], [], timeout)
  if len(inp) != 0:
  data = udpsock.recv(1024)
  pkt = EncapsulationPacket()
  pkt.unpack(data)
  if pkt.status == EncapsulationPacket.ENCAP_STATUS_SUCCESS and pkt.command == EncapsulationPacket.ENCAP_CMD_LISTIDENTITY:
  csd = CommandSpecificData(pkt.data)
  if csd.type_id == CommandSpecificData.TYPE_ID_LIST_IDENT_RESPONSE:
  lid = ListIdentifyReply(csd.data)
  return lid
  return None
   
  class EtherNetIPSocket(object):
  def __init__(self, ip):
  self.ipaddr = ip
  self.sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
  self.sock.connect((self.ipaddr, ENIP_TCP_PORT))
  self.conn_serial_num = 0
   
  def delete(self):
  self.sock.close()
   
  def mkReqPath(self, clas, inst, attr):
  if clas > 255:
  clas_data = struct.pack("BBH", 0x21, 0, 0x300)
  else:
  clas_data = struct.pack("BB", 0x20, clas)
  inst_data = struct.pack("BB", 0x24, inst)
  attr_data = b''
  if attr != None:
  attr_data = struct.pack("BB", 0x30, attr)
  data = bytes([(int((len(clas_data) + len(inst_data) + len(attr_data))/2))])
  data += clas_data
  data += inst_data
  data += attr_data
  return data
   
  def scanNetwork(self, broadcastAddress="255.255.255.0", timeout=10):
  import time
  listOfNodes = []
  udpsock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
  #udpsock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
  udpsock.setsockopt(socket.SOL_SOCKET, socket.SO_BROADCAST, 1)
  context = random.randint(1, 4026531839)
  pkt = EncapsulationPacket(command=EncapsulationPacket.ENCAP_CMD_LISTIDENTITY,
  sender_context=context.to_bytes(8, byteorder='big'))
  udpsock.sendto(pkt.pack(),(broadcastAddress, ENIP_TCP_PORT))
  tStart = time.time()
  while time.time() < (tStart+timeout):
  timeout = tStart + timeout - time.time()
  inp, out, err = select.select([udpsock], [], [], timeout)
  if len(inp) != 0:
  data = udpsock.recv(1024)
  pkt = EncapsulationPacket()
  pkt.unpack(data)
  if pkt.status == EncapsulationPacket.ENCAP_STATUS_SUCCESS and pkt.command == EncapsulationPacket.ENCAP_CMD_LISTIDENTITY:
  csd = CommandSpecificData(pkt.data)
  if csd.type_id == CommandSpecificData.TYPE_ID_LIST_IDENT_RESPONSE:
  lid = ListIdentifyReply(csd.data)
  listOfNodes.append(lid)
  return listOfNodes
   
  def listID(self):
  context = random.randint(1, 4026531839)
  pkt = EncapsulationPacket(command=EncapsulationPacket.ENCAP_CMD_LISTIDENTITY,
  sender_context=context.to_bytes(8, byteorder='big'))
  self.sock.send(pkt.pack())
  inp, out, err = select.select([self.sock], [], [], 10)
  if len(inp) != 0:
  data = self.sock.recv(1024)
  pkt = EncapsulationPacket()
  pkt.unpack(data)
  if pkt.status == EncapsulationPacket.ENCAP_STATUS_SUCCESS and pkt.command == EncapsulationPacket.ENCAP_CMD_LISTIDENTITY:
  csd = CommandSpecificData(pkt.data)
  if csd.type_id == CommandSpecificData.TYPE_ID_LIST_IDENT_RESPONSE:
  lid = ListIdentifyReply(csd.data)
  return lid
  return None
   
  def listServices(self):
  context = random.randint(1,4026531839)
  pkt = EncapsulationPacket(command=EncapsulationPacket.ENCAP_CMD_LISTSERVICES,
  sender_context=context.to_bytes(8, byteorder='big'))
  self.sock.send(pkt.pack())
  inp, out, err = select.select([self.sock], [], [], 10)
  if len(inp) != 0:
  data = self.sock.recv(1024)
  pkt = EncapsulationPacket()
  pkt.unpack(data)
  if pkt.status == EncapsulationPacket.ENCAP_STATUS_SUCCESS and pkt.command == EncapsulationPacket.ENCAP_CMD_LISTSERVICES:
  csd = CommandSpecificData(pkt.data)
  if csd.type_id == CommandSpecificData.TYPE_ID_LISTSERVICES_RESPONSE:
  lsr = ListServicesReply(csd.data)
  return lsr
  return None
   
  class EtherNetIPSession(EtherNetIPSocket):
  def __init__(self, ipaddr):
  EtherNetIPSocket.__init__(self,ipaddr)
  self.session = 0
   
  def delete(self):
  self.session = 0
   
  def registerSession(self):
  context = random.randint(1,4026531839)
  csd = RegisterSessionPacket(protocol_version=1, option_flag=0)
  pkt = EncapsulationPacket(command=EncapsulationPacket.ENCAP_CMD_REGISTERSESSION,\
  length=len(csd), sender_context=context.to_bytes(8, byteorder='big'), data=csd)
  self.sock.send(pkt.pack())
  inp, out, err = select.select([self.sock], [], [], 10)
  if len(inp) != 0:
  data = self.sock.recv(1024)
  pkt = EncapsulationPacket()
  pkt.unpack(data)
  if pkt.status == EncapsulationPacket.ENCAP_STATUS_SUCCESS and pkt.command == EncapsulationPacket.ENCAP_CMD_REGISTERSESSION:
  self.session = pkt.session
  return 0
  return None
   
  def unregisterSession(self):
  context = random.randint(1,4026531839)
  pkt = EncapsulationPacket(command=EncapsulationPacket.ENCAP_CMD_UNREGISTERSESSION,\
  length=0, session=self.session, sender_context=context.to_bytes(8, byteorder='big'), data=b'')
  self.sock.send(pkt.pack())
  self.session=0
   
   
  def sendEncap(self, command, data):
  context = random.randint(1,4026531839)
  pkt = EncapsulationPacket(command=command, length=len(data), sender_context=context.to_bytes(8, byteorder='big'), data=data)
  return self.sock.send(pkt.pack())
   
  def unconnSend(self, service, data, context=0, chk=0, chkdata="\x00"):
  sz = len(data) + 17
  # add service field
  dsz = len(data) + 1
  cpf2 = \
  UnconnectedDataItem(type_id=CommandSpecificData.TYPE_ID_UNCONNECTED_MESSAGE, \
  length=dsz, data=data, \
  service=(service|UnconnectedDataItem.UNCONN_DATA_ITEM_SERVICE_REQUEST))
  cpf = CommandSpecificData(type_id=CommandSpecificData.TYPE_ID_NULL, \
  item_count=2, length=0, data=cpf2)
  srr = SendRRPacket(interface_handle=0, timeout=10, data=cpf);
  pkt = EncapsulationPacket(command=EncapsulationPacket.ENCAP_CMD_SENDRRDATA, \
  length=len(srr),session=self.session, sender_context=context.to_bytes(8, byteorder='big'), data=srr)
  self.sock.send(pkt.pack())
  inp, out, err = select.select([self.sock], [], [], 10)
  if len(inp) != 0:
  data = self.sock.recv(4096)
  if len(data) > 0:
  pkt = EncapsulationPacket()
  pkt.unpack(data)
  if pkt.status == EncapsulationPacket.ENCAP_STATUS_SUCCESS and \
  pkt.command == EncapsulationPacket.ENCAP_CMD_SENDRRDATA:
  srr = SendRRPacket(pkt.data)
  csd = CommandSpecificData(srr.data)
  cpf = UnconnectedDataItemResp(csd.data)
  ret = [cpf.status, cpf.data]
  if chk != 0:
  rsppkt = self.unconnSendValidRsp(service, chkdata, context)
  if str(rsppkt) != str(pkt):
  print("Packets differ")
  assert(0)
  return ret