From e1fc5c81a4e67da8b158213d5b1f3c91012a36ce Mon Sep 17 00:00:00 2001 From: Patrick McDonagh Date: Tue, 1 Mar 2016 12:59:44 -0600 Subject: [PATCH 1/2] Added files to attempt reading from micro800 --- python/micro800.py | 94 +++ python/pycomm_micro/__init__.py | 1 + python/pycomm_micro/__init__.pyc | Bin 0 -> 195 bytes python/pycomm_micro/ab_comm/__init__.py | 2 + python/pycomm_micro/ab_comm/__init__.pyc | Bin 0 -> 234 bytes python/pycomm_micro/ab_comm/clx.py | 847 +++++++++++++++++++++++ python/pycomm_micro/ab_comm/clx.pyc | Bin 0 -> 24962 bytes python/pycomm_micro/ab_comm/slc.py | 446 ++++++++++++ python/pycomm_micro/ab_comm/slc.pyc | Bin 0 -> 12028 bytes python/pycomm_micro/cip/__init__.py | 1 + python/pycomm_micro/cip/__init__.pyc | Bin 0 -> 199 bytes python/pycomm_micro/cip/cip_base.py | 827 ++++++++++++++++++++++ python/pycomm_micro/cip/cip_base.pyc | Bin 0 -> 29398 bytes python/pycomm_micro/cip/cip_const.py | 482 +++++++++++++ python/pycomm_micro/cip/cip_const.pyc | Bin 0 -> 10713 bytes python/pycomm_micro/common.py | 32 + python/pycomm_micro/common.pyc | Bin 0 -> 1306 bytes python/solar_ww.py | 67 -- 18 files changed, 2732 insertions(+), 67 deletions(-) create mode 100644 python/micro800.py create mode 100644 python/pycomm_micro/__init__.py create mode 100644 python/pycomm_micro/__init__.pyc create mode 100644 python/pycomm_micro/ab_comm/__init__.py create mode 100644 python/pycomm_micro/ab_comm/__init__.pyc create mode 100644 python/pycomm_micro/ab_comm/clx.py create mode 100644 python/pycomm_micro/ab_comm/clx.pyc create mode 100644 python/pycomm_micro/ab_comm/slc.py create mode 100644 python/pycomm_micro/ab_comm/slc.pyc create mode 100644 python/pycomm_micro/cip/__init__.py create mode 100644 python/pycomm_micro/cip/__init__.pyc create mode 100644 python/pycomm_micro/cip/cip_base.py create mode 100644 python/pycomm_micro/cip/cip_base.pyc create mode 100644 python/pycomm_micro/cip/cip_const.py create mode 100644 python/pycomm_micro/cip/cip_const.pyc create mode 100644 python/pycomm_micro/common.py create mode 100644 python/pycomm_micro/common.pyc delete mode 100644 python/solar_ww.py diff --git a/python/micro800.py b/python/micro800.py new file mode 100644 index 0000000..e476982 --- /dev/null +++ b/python/micro800.py @@ -0,0 +1,94 @@ +from pycomm_micro.ab_comm.clx import Driver as ClxDriver +import logging +import sys + + +def readMicroTag(addr, tag): + logging.basicConfig( + filename="ClxDriver.log", + format="%(levelname)-10s %(asctime)s %(message)s", + level=logging.DEBUG + ) + c = ClxDriver() + + if c.open(addr): + try: + v = c.read_tag(tag) + # print(v) + return v + except Exception as e: + err = c.get_status() + c.close() + print err + pass + c.close() + +def getTagType(addr, tag): + logging.basicConfig( + filename="ClxDriver.log", + format="%(levelname)-10s %(asctime)s %(message)s", + level=logging.DEBUG + ) + c = ClxDriver() + + if c.open(addr): + try: + return c.read_tag(tag)[1] + except Exception as e: + err = c.get_status() + c.close() + print err + pass + c.close() + +def writeMicroTag(addr, tag, val): + logging.basicConfig( + filename="ClxDriver.log", + format="%(levelname)-10s %(asctime)s %(message)s", + level=logging.DEBUG + ) + c = ClxDriver() + + if c.open(addr): + try: + #typ = getTagType(addr, tag) + cv = c.read_tag(tag) + wt = c.write_tag(tag, val, cv[1]) + # print(wt) + return wt + except Exception as e: + err = c.get_status() + c.close() + print err + pass + c.close() + + +def readMicroTagList(addr, tList): + logging.basicConfig( + filename="ClxDriver.log", + format="%(levelname)-10s %(asctime)s %(message)s", + level=logging.DEBUG + ) + c = ClxDriver() + + if c.open(addr): + vals = [] + try: + for t in tList: + v = c.read_tag(t) + vals.append({"tag":t,"val":v[0], "type":v[1]}) + # print(v) + # print("{0} - {1}".format(t, v)) + except Exception as e: + err = c.get_status() + c.close() + print err + pass + c.close() + return vals +if __name__ == '__main__': + if len(sys.argv) > 2: + print(readMicroTag(sys.argv[1], sys.argv[2])) + else: + print ("Did not pass a target and tag name.") diff --git a/python/pycomm_micro/__init__.py b/python/pycomm_micro/__init__.py new file mode 100644 index 0000000..8c1f233 --- /dev/null +++ b/python/pycomm_micro/__init__.py @@ -0,0 +1 @@ +__author__ = 'agostino' diff --git a/python/pycomm_micro/__init__.pyc b/python/pycomm_micro/__init__.pyc new file mode 100644 index 0000000000000000000000000000000000000000..a2df465900e44f20dd30cff645774776abf5b792 GIT binary patch literal 195 zcmYL?u?oUK5JWH9NWc%UvQ1+SY-~iVEo?*!n`ON_;+foC$Zm>!lHcbKxCep@^Ok`b zX8u{0tM~e5)b9%UM-i0*tJGass7o~y%L@TEiP*<gW*ttVq HCz1LBHy)Aeg}7wi`?>50;>2$Hda7%>^MI%36Su318@upppzJ{arFhvJ-#yCEe2U>_c? z0|;{}Yp1obIwQ;e@E0oi&8yDy7DH!5^=yRXXi+#Tk=Lf=?Lc?zF5h!+g4(tyD%6S= g2H~29V+oT27;6GRPV?hqhsMdiR>z3EAK#iqUkIf;KL7v# literal 0 HcmV?d00001 diff --git a/python/pycomm_micro/ab_comm/clx.py b/python/pycomm_micro/ab_comm/clx.py new file mode 100644 index 0000000..0ba1586 --- /dev/null +++ b/python/pycomm_micro/ab_comm/clx.py @@ -0,0 +1,847 @@ +# -*- coding: utf-8 -*- +# +# clx.py - Ethernet/IP Client for Rockwell PLCs +# +# +# Copyright (c) 2014 Agostino Ruscito +# +# Permission is hereby granted, free of charge, to any person obtaining a copy +# of this software and associated documentation files (the "Software"), to deal +# in the Software without restriction, including without limitation the rights +# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +# copies of the Software, and to permit persons to whom the Software is +# furnished to do so, subject to the following conditions: +# +# The above copyright notice and this permission notice shall be included in all +# copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +# SOFTWARE. +# +from pycomm_micro.cip.cip_base import * +from pycomm_micro.common import setup_logger +import logging + + +class Driver(Base): + """ + This Ethernet/IP client is based on Rockwell specification. Please refer to the link below for details. + + http://literature.rockwellautomation.com/idc/groups/literature/documents/pm/1756-pm020_-en-p.pdf + + The following services have been implemented: + - Read Tag Service (0x4c) + - Read Tag Fragment Service (0x52) + - Write Tag Service (0x4d) + - Write Tag Fragment Service (0x53) + - Multiple Service Packet (0x0a) + + The client has been successfully tested with the following PLCs: + - CompactLogix 5330ERM + - CompactLogix 5370 + - ControlLogix 5572 and 1756-EN2T Module + +""" + def __init__(self, debug=False, filename=None): + if debug: + super(Driver, self).__init__(setup_logger('ab_comm.clx', logging.DEBUG, filename)) + else: + super(Driver, self).__init__(setup_logger('ab_comm.clx', logging.INFO, filename)) + + self._buffer = {} + self._get_template_in_progress = False + self.__version__ = '0.2' + + def get_last_tag_read(self): + """ Return the last tag read by a multi request read + + :return: A tuple (tag name, value, type) + """ + return self._last_tag_read + + def get_last_tag_write(self): + """ Return the last tag write by a multi request write + + :return: A tuple (tag name, 'GOOD') if the write was successful otherwise (tag name, 'BAD') + """ + return self._last_tag_write + + def _parse_instance_attribute_list(self, start_tag_ptr, status): + """ extract the tags list from the message received + + :param start_tag_ptr: The point in the message string where the tag list begin + :param status: The status of the message receives + """ + tags_returned = self._reply[start_tag_ptr:] + tags_returned_length = len(tags_returned) + idx = 0 + instance = 0 + count = 0 + try: + while idx < tags_returned_length: + instance = unpack_dint(tags_returned[idx:idx+4]) + idx += 4 + tag_length = unpack_uint(tags_returned[idx:idx+2]) + idx += 2 + tag_name = tags_returned[idx:idx+tag_length] + idx += tag_length + symbol_type = unpack_uint(tags_returned[idx:idx+2]) + idx += 2 + count += 1 + self._tag_list.append({'instance_id': instance, + 'tag_name': tag_name, + 'symbol_type': symbol_type}) + except Exception as e: + raise DataError(e) + + if status == SUCCESS: + self._last_instance = -1 + elif status == 0x06: + self._last_instance = instance + 1 + else: + self._status = (1, 'unknown status during _parse_tag_list') + self._last_instance = -1 + + def _parse_structure_makeup_attributes(self, start_tag_ptr, status): + """ extract the tags list from the message received + + :param start_tag_ptr: The point in the message string where the tag list begin + :param status: The status of the message receives + """ + self._buffer = {} + + if status != SUCCESS: + self._buffer['Error'] = status + return + + attribute = self._reply[start_tag_ptr:] + idx = 4 + try: + if unpack_uint(attribute[idx:idx + 2]) == SUCCESS: + idx += 2 + self._buffer['object_definition_size'] = unpack_dint(attribute[idx:idx + 4]) + else: + self._buffer['Error'] = 'object_definition Error' + return + + idx += 6 + if unpack_uint(attribute[idx:idx + 2]) == SUCCESS: + idx += 2 + self._buffer['structure_size'] = unpack_dint(attribute[idx:idx + 4]) + else: + self._buffer['Error'] = 'structure Error' + return + + idx += 6 + if unpack_uint(attribute[idx:idx + 2]) == SUCCESS: + idx += 2 + self._buffer['member_count'] = unpack_uint(attribute[idx:idx + 2]) + else: + self._buffer['Error'] = 'member_count Error' + return + + idx += 4 + if unpack_uint(attribute[idx:idx + 2]) == SUCCESS: + idx += 2 + self._buffer['structure_handle'] = unpack_uint(attribute[idx:idx + 2]) + else: + self._buffer['Error'] = 'structure_handle Error' + return + + return self._buffer + + except Exception as e: + raise DataError(e) + + def _parse_template(self, start_tag_ptr, status): + """ extract the tags list from the message received + + :param start_tag_ptr: The point in the message string where the tag list begin + :param status: The status of the message receives + """ + tags_returned = self._reply[start_tag_ptr:] + bytes_received = len(tags_returned) + + self._buffer += tags_returned + + if status == SUCCESS: + self._get_template_in_progress = False + + elif status == 0x06: + self._byte_offset += bytes_received + else: + self._status = (1, 'unknown status {0} during _parse_template'.format(status)) + self.logger.warning(self._status) + self._last_instance = -1 + + def _parse_fragment(self, start_ptr, status): + """ parse the fragment returned by a fragment service. + + :param start_ptr: Where the fragment start within the replay + :param status: status field used to decide if keep parsing or stop + """ + try: + data_type = unpack_uint(self._reply[start_ptr:start_ptr+2]) + fragment_returned = self._reply[start_ptr+2:] + except Exception as e: + raise DataError(e) + + fragment_returned_length = len(fragment_returned) + idx = 0 + + while idx < fragment_returned_length: + try: + typ = I_DATA_TYPE[data_type] + value = UNPACK_DATA_FUNCTION[typ](fragment_returned[idx:idx+DATA_FUNCTION_SIZE[typ]]) + idx += DATA_FUNCTION_SIZE[typ] + except Exception as e: + raise DataError(e) + self._tag_list.append((self._last_position, value)) + self._last_position += 1 + + if status == SUCCESS: + self._byte_offset = -1 + elif status == 0x06: + self._byte_offset += fragment_returned_length + else: + self._status = (2, 'unknown status during _parse_fragment') + self._byte_offset = -1 + + def _parse_multiple_request_read(self, tags): + """ parse the message received from a multi request read: + + For each tag parsed, the information extracted includes the tag name, the value read and the data type. + Those information are appended to the tag list as tuple + + :return: the tag list + """ + offset = 50 + position = 50 + try: + number_of_service_replies = unpack_uint(self._reply[offset:offset+2]) + tag_list = [] + for index in range(number_of_service_replies): + position += 2 + start = offset + unpack_uint(self._reply[position:position+2]) + general_status = unpack_usint(self._reply[start+2:start+3]) + + if general_status == 0: + data_type = unpack_uint(self._reply[start+4:start+6]) + value_begin = start + 6 + value_end = value_begin + DATA_FUNCTION_SIZE[I_DATA_TYPE[data_type]] + value = self._reply[value_begin:value_end] + self._last_tag_read = (tags[index], UNPACK_DATA_FUNCTION[I_DATA_TYPE[data_type]](value), + I_DATA_TYPE[data_type]) + else: + self._last_tag_read = (tags[index], None, None) + + tag_list.append(self._last_tag_read) + + return tag_list + except Exception as e: + raise DataError(e) + + def _parse_multiple_request_write(self, tags): + """ parse the message received from a multi request writ: + + For each tag parsed, the information extracted includes the tag name and the status of the writing. + Those information are appended to the tag list as tuple + + :return: the tag list + """ + offset = 50 + position = 50 + try: + number_of_service_replies = unpack_uint(self._reply[offset:offset+2]) + tag_list = [] + for index in range(number_of_service_replies): + position += 2 + start = offset + unpack_uint(self._reply[position:position+2]) + general_status = unpack_usint(self._reply[start+2:start+3]) + + if general_status == 0: + self._last_tag_write = (tags[index] + ('GOOD',)) + else: + self._last_tag_write = (tags[index] + ('BAD',)) + + tag_list.append(self._last_tag_write) + return tag_list + except Exception as e: + raise DataError(e) + + def _check_reply(self): + """ check the replayed message for error + + """ + self._more_packets_available = False + try: + if self._reply is None: + self._status = (3, '%s without reply' % REPLAY_INFO[unpack_dint(self._message[:2])]) + return False + # Get the type of command + typ = unpack_uint(self._reply[:2]) + + # Encapsulation status check + if unpack_dint(self._reply[8:12]) != SUCCESS: + self._status = (3, "{0} reply status:{1}".format(REPLAY_INFO[typ], + SERVICE_STATUS[unpack_dint(self._reply[8:12])])) + return False + + # Command Specific Status check + if typ == unpack_uint(ENCAPSULATION_COMMAND["send_rr_data"]): + status = unpack_usint(self._reply[42:43]) + if status != SUCCESS: + self._status = (3, "send_rr_data reply:{0} - Extend status:{1}".format( + SERVICE_STATUS[status], get_extended_status(self._reply, 42))) + return False + else: + return True + elif typ == unpack_uint(ENCAPSULATION_COMMAND["send_unit_data"]): + status = unpack_usint(self._reply[48:49]) + if unpack_usint(self._reply[46:47]) == I_TAG_SERVICES_REPLY["Read Tag Fragmented"]: + self._parse_fragment(50, status) + return True + if unpack_usint(self._reply[46:47]) == I_TAG_SERVICES_REPLY["Get Instance Attributes List"]: + self._parse_instance_attribute_list(50, status) + return True + if unpack_usint(self._reply[46:47]) == I_TAG_SERVICES_REPLY["Get Attributes"]: + self._parse_structure_makeup_attributes(50, status) + return True + if unpack_usint(self._reply[46:47]) == I_TAG_SERVICES_REPLY["Read Template"] and \ + self._get_template_in_progress: + self._parse_template(50, status) + return True + if status == 0x06: + self._status = (3, "Insufficient Packet Space") + self._more_packets_available = True + elif status != SUCCESS: + self._status = (3, "send_unit_data reply:{0} - Extend status:{1}".format( + SERVICE_STATUS[status], get_extended_status(self._reply, 48))) + return False + else: + return True + + return True + except Exception as e: + raise DataError(e) + + def read_tag(self, tag): + """ read tag from a connected plc + + Possible combination can be passed to this method: + - ('Counts') a single tag name + - (['ControlWord']) a list with one tag or many + - (['parts', 'ControlWord', 'Counts']) + + At the moment there is not a strong validation for the argument passed. The user should verify + the correctness of the format passed. + + :return: None is returned in case of error otherwise the tag list is returned + """ + multi_requests = False + if isinstance(tag, list): + multi_requests = True + + if not self._target_is_connected: + if not self.forward_open(): + self._status = (6, "Target did not connected. read_tag will not be executed.") + self.logger.warning(self._status) + raise Error("Target did not connected. read_tag will not be executed.") + # multi_requests = False + if multi_requests: + rp_list = [] + for t in tag: + rp = create_tag_rp(t, multi_requests=True) + if rp is None: + self._status = (6, "Cannot create tag {0} request packet. read_tag will not be executed.".format(tag)) + raise DataError("Cannot create tag {0} request packet. read_tag will not be executed.".format(tag)) + else: + rp_list.append(chr(TAG_SERVICES_REQUEST['Read Tag']) + rp + pack_uint(1)) + message_request = build_multiple_service(rp_list, Base._get_sequence()) + + else: + rp = create_tag_rp(tag) + if rp is None: + self._status = (6, "Cannot create tag {0} request packet. read_tag will not be executed.".format(tag)) + return None + else: + # Creating the Message Request Packet + message_request = [ + pack_uint(Base._get_sequence()), + chr(TAG_SERVICES_REQUEST['Read Tag']), # the Request Service + chr(len(rp) / 2), # the Request Path Size length in word + rp, # the request path + pack_uint(1) + ] + + if self.send_unit_data( + build_common_packet_format( + DATA_ITEM['Connected'], + ''.join(message_request), + ADDRESS_ITEM['Connection Based'], + addr_data=self._target_cid, + )) is None: + raise DataError("send_unit_data returned not valid data") + + if multi_requests: + return self._parse_multiple_request_read(tag) + else: + # Get the data type + data_type = unpack_uint(self._reply[50:52]) + # print I_DATA_TYPE[data_type] + try: + return UNPACK_DATA_FUNCTION[I_DATA_TYPE[data_type]](self._reply[52:]), I_DATA_TYPE[data_type] + except Exception as e: + raise DataError(e) + + def read_array(self, tag, counts): + """ read array of atomic data type from a connected plc + + At the moment there is not a strong validation for the argument passed. The user should verify + the correctness of the format passed. + + :param tag: the name of the tag to read + :param counts: the number of element to read + :return: None is returned in case of error otherwise the tag list is returned + """ + if not self._target_is_connected: + if not self.forward_open(): + self._status = (7, "Target did not connected. read_tag will not be executed.") + self.logger.warning(self._status) + raise Error("Target did not connected. read_tag will not be executed.") + + self._byte_offset = 0 + self._last_position = 0 + + self._tag_list = [] + while self._byte_offset != -1: + rp = create_tag_rp(tag) + if rp is None: + self._status = (7, "Cannot create tag {0} request packet. read_tag will not be executed.".format(tag)) + return None + else: + # Creating the Message Request Packet + message_request = [ + pack_uint(Base._get_sequence()), + chr(TAG_SERVICES_REQUEST["Read Tag Fragmented"]), # the Request Service + chr(len(rp) / 2), # the Request Path Size length in word + rp, # the request path + pack_uint(counts), + pack_dint(self._byte_offset) + ] + + if self.send_unit_data( + build_common_packet_format( + DATA_ITEM['Connected'], + ''.join(message_request), + ADDRESS_ITEM['Connection Based'], + addr_data=self._target_cid, + )) is None: + raise DataError("send_unit_data returned not valid data") + + return self._tag_list + + def write_tag(self, tag, value=None, typ=None): + """ write tag/tags from a connected plc + + Possible combination can be passed to this method: + - ('tag name', Value, data type) as single parameters or inside a tuple + - ([('tag name', Value, data type), ('tag name2', Value, data type)]) as array of tuples + + At the moment there is not a strong validation for the argument passed. The user should verify + the correctness of the format passed. + + The type accepted are: + - BOOL + - SINT + - INT' + - DINT + - REAL + - LINT + - BYTE + - WORD + - DWORD + - LWORD + + :param tag: tag name, or an array of tuple containing (tag name, value, data type) + :param value: the value to write or none if tag is an array of tuple or a tuple + :param typ: the type of the tag to write or none if tag is an array of tuple or a tuple + :return: None is returned in case of error otherwise the tag list is returned + """ + multi_requests = False + if isinstance(tag, list): + multi_requests = True + + if not self._target_is_connected: + if not self.forward_open(): + self._status = (8, "Target did not connected. write_tag will not be executed.") + self.logger.warning(self._status) + raise Error("Target did not connected. write_tag will not be executed.") + + if multi_requests: + rp_list = [] + tag_to_remove = [] + idx = 0 + for name, value, typ in tag: + # Create the request path to wrap the tag name + rp = create_tag_rp(name, multi_requests=True) + if rp is None: + self._status = (8, "Cannot create tag{0} req. packet. write_tag will not be executed".format(tag)) + return None + else: + try: # Trying to add the rp to the request path list + val = PACK_DATA_FUNCTION[typ](value) + rp_list.append( + chr(TAG_SERVICES_REQUEST['Write Tag']) + + rp + + pack_uint(S_DATA_TYPE[typ]) + + pack_uint(1) + + val + ) + idx += 1 + except (LookupError, struct.error) as e: + self._status = (8, "Tag:{0} type:{1} removed from write list. Error:{2}.".format(name, typ, e)) + + # The tag in idx position need to be removed from the rp list because has some kind of error + tag_to_remove.append(idx) + + # Remove the tags that have not been inserted in the request path list + for position in tag_to_remove: + del tag[position] + # Create the message request + message_request = build_multiple_service(rp_list, Base._get_sequence()) + + else: + if isinstance(tag, tuple): + name, value, typ = tag + else: + name = tag + + rp = create_tag_rp(name) + if rp is None: + self._status = (8, "Cannot create tag {0} request packet. write_tag will not be executed.".format(tag)) + self.logger.warning(self._status) + return None + else: + # Creating the Message Request Packet + message_request = [ + pack_uint(Base._get_sequence()), + chr(TAG_SERVICES_REQUEST["Write Tag"]), # the Request Service + chr(len(rp) / 2), # the Request Path Size length in word + rp, # the request path + pack_uint(S_DATA_TYPE[typ]), # data type + pack_uint(1), # Add the number of tag to write + PACK_DATA_FUNCTION[typ](value) + ] + + ret_val = self.send_unit_data( + build_common_packet_format( + DATA_ITEM['Connected'], + ''.join(message_request), + ADDRESS_ITEM['Connection Based'], + addr_data=self._target_cid, + ) + ) + + if multi_requests: + return self._parse_multiple_request_write(tag) + else: + if ret_val is None: + raise DataError("send_unit_data returned not valid data") + return ret_val + + def write_array(self, tag, data_type, values): + """ write array of atomic data type from a connected plc + + At the moment there is not a strong validation for the argument passed. The user should verify + the correctness of the format passed. + + :param tag: the name of the tag to read + :param data_type: the type of tag to write + :param values: the array of values to write + """ + if not isinstance(values, list): + self._status = (9, "A list of tags must be passed to write_array.") + self.logger.warning(self._status) + raise DataError("A list of tags must be passed to write_array.") + + if not self._target_is_connected: + if not self.forward_open(): + self._status = (9, "Target did not connected. write_array will not be executed.") + self.logger.warning(self._status) + raise Error("Target did not connected. write_array will not be executed.") + + array_of_values = "" + byte_size = 0 + byte_offset = 0 + + for i, value in enumerate(values): + array_of_values += PACK_DATA_FUNCTION[data_type](value) + byte_size += DATA_FUNCTION_SIZE[data_type] + + if byte_size >= 450 or i == len(values)-1: + # create the message and send the fragment + rp = create_tag_rp(tag) + if rp is None: + self._status = (9, "Cannot create tag {0} request packet. \ + write_array will not be executed.".format(tag)) + return None + else: + # Creating the Message Request Packet + message_request = [ + pack_uint(Base._get_sequence()), + chr(TAG_SERVICES_REQUEST["Write Tag Fragmented"]), # the Request Service + chr(len(rp) / 2), # the Request Path Size length in word + rp, # the request path + pack_uint(S_DATA_TYPE[data_type]), # Data type to write + pack_uint(len(values)), # Number of elements to write + pack_dint(byte_offset), + array_of_values # Fragment of elements to write + ] + byte_offset += byte_size + + if self.send_unit_data( + build_common_packet_format( + DATA_ITEM['Connected'], + ''.join(message_request), + ADDRESS_ITEM['Connection Based'], + addr_data=self._target_cid, + )) is None: + raise DataError("send_unit_data returned not valid data") + array_of_values = "" + byte_size = 0 + + def _get_instance_attribute_list_service(self): + """ Step 1: Finding user-created controller scope tags in a Logix5000 controller + + This service returns instance IDs for each created instance of the symbol class, along with a list + of the attribute data associated with the requested attribute + """ + try: + if not self._target_is_connected: + if not self.forward_open(): + self._status = (10, "Target did not connected. get_tag_list will not be executed.") + self.logger.warning(self._status) + raise Error("Target did not connected. get_tag_list will not be executed.") + + self._last_instance = 0 + + self._get_template_in_progress = True + while self._last_instance != -1: + + # Creating the Message Request Packet + + message_request = [ + pack_uint(Base._get_sequence()), + chr(TAG_SERVICES_REQUEST['Get Instance Attributes List']), # STEP 1 + # the Request Path Size length in word + chr(3), + # Request Path ( 20 6B 25 00 Instance ) + CLASS_ID["8-bit"], # Class id = 20 from spec 0x20 + CLASS_CODE["Symbol Object"], # Logical segment: Symbolic Object 0x6B + INSTANCE_ID["16-bit"], # Instance Segment: 16 Bit instance 0x25 + '\x00', + pack_uint(self._last_instance), # The instance + # Request Data + pack_uint(2), # Number of attributes to retrieve + pack_uint(1), # Attribute 1: Symbol name + pack_uint(2) # Attribute 2: Symbol type + ] + + if self.send_unit_data( + build_common_packet_format( + DATA_ITEM['Connected'], + ''.join(message_request), + ADDRESS_ITEM['Connection Based'], + addr_data=self._target_cid, + )) is None: + raise DataError("send_unit_data returned not valid data") + + self._get_template_in_progress = False + + except Exception as e: + raise DataError(e) + + def _get_structure_makeup(self, instance_id): + """ + get the structure makeup for a specific structure + """ + if not self._target_is_connected: + if not self.forward_open(): + self._status = (10, "Target did not connected. get_tag_list will not be executed.") + self.logger.warning(self._status) + raise Error("Target did not connected. get_tag_list will not be executed.") + + message_request = [ + pack_uint(self._get_sequence()), + chr(TAG_SERVICES_REQUEST['Get Attributes']), + chr(3), # Request Path ( 20 6B 25 00 Instance ) + CLASS_ID["8-bit"], # Class id = 20 from spec 0x20 + CLASS_CODE["Template Object"], # Logical segment: Template Object 0x6C + INSTANCE_ID["16-bit"], # Instance Segment: 16 Bit instance 0x25 + '\x00', + pack_uint(instance_id), + pack_uint(4), # Number of attributes + pack_uint(4), # Template Object Definition Size UDINT + pack_uint(5), # Template Structure Size UDINT + pack_uint(2), # Template Member Count UINT + pack_uint(1) # Structure Handle We can use this to read and write UINT + ] + + if self.send_unit_data( + build_common_packet_format(DATA_ITEM['Connected'], + ''.join(message_request), ADDRESS_ITEM['Connection Based'], + addr_data=self._target_cid,)) is None: + raise DataError("send_unit_data returned not valid data") + + return self._buffer + + def _read_template(self, instance_id, object_definition_size): + """ get a list of the tags in the plc + + """ + if not self._target_is_connected: + if not self.forward_open(): + self._status = (10, "Target did not connected. get_tag_list will not be executed.") + self.logger.warning(self._status) + raise Error("Target did not connected. get_tag_list will not be executed.") + + self._byte_offset = 0 + self._buffer = "" + self._get_template_in_progress = True + + try: + while self._get_template_in_progress: + + # Creating the Message Request Packet + + message_request = [ + pack_uint(self._get_sequence()), + chr(TAG_SERVICES_REQUEST['Read Template']), + chr(3), # Request Path ( 20 6B 25 00 Instance ) + CLASS_ID["8-bit"], # Class id = 20 from spec 0x20 + CLASS_CODE["Template Object"], # Logical segment: Template Object 0x6C + INSTANCE_ID["16-bit"], # Instance Segment: 16 Bit instance 0x25 + '\x00', + pack_uint(instance_id), + pack_dint(self._byte_offset), # Offset + pack_uint(((object_definition_size * 4)-23) - self._byte_offset) + ] + + if not self.send_unit_data( + build_common_packet_format(DATA_ITEM['Connected'], ''.join(message_request), + ADDRESS_ITEM['Connection Based'], addr_data=self._target_cid,)): + raise DataError("send_unit_data returned not valid data") + + self._get_template_in_progress = False + return self._buffer + + except Exception as e: + raise DataError(e) + + def _isolating_user_tag(self): + try: + lst = self._tag_list + self._tag_list = [] + for tag in lst: + if tag['tag_name'].find(':') != -1 or tag['tag_name'].find('__') != -1: + continue + if tag['symbol_type'] & 0b0001000000000000: + continue + dimension = tag['symbol_type'] & 0b0110000000000000 >> 13 + template_instance_id = tag['symbol_type'] & 0b0000111111111111 + + if tag['symbol_type'] & 0b1000000000000000 : + tag_type = 'struct' + data_type = 'user-created' + self._tag_list.append({'instance_id': tag['instance_id'], + 'template_instance_id': template_instance_id, + 'tag_name': tag['tag_name'], + 'dim': dimension, + 'tag_type': tag_type, + 'data_type': data_type, + 'template': {}, + 'udt': {}}) + else: + tag_type = 'atomic' + data_type = I_DATA_TYPE[template_instance_id] + self._tag_list.append({'instance_id': tag['instance_id'], + 'tag_name': tag['tag_name'], + 'dim': dimension, + 'tag_type': tag_type, + 'data_type': data_type}) + except Exception as e: + raise DataError(e) + + def _parse_udt_raw(self, tag): + try: + buff = self._read_template(tag['template_instance_id'], tag['template']['object_definition_size']) + member_count = tag['template']['member_count'] + names = buff.split('\00') + lst = [] + + tag['udt']['name'] = 'Not an user defined structure' + for name in names: + if len(name) > 1: + + if name.find(';') != -1: + tag['udt']['name'] = name[:name.find(';')] + elif name.find('ZZZZZZZZZZ') != -1: + continue + elif name.isalpha(): + lst.append(name) + else: + continue + tag['udt']['internal_tags'] = lst + + type_list = [] + + for i in xrange(member_count): + # skip member 1 + + if i != 0: + array_size = unpack_uint(buff[:2]) + try: + data_type = I_DATA_TYPE[unpack_uint(buff[2:4])] + except Exception: + data_type = "None" + + offset = unpack_dint(buff[4:8]) + type_list.append((array_size, data_type, offset)) + + buff = buff[8:] + + tag['udt']['data_type'] = type_list + except Exception as e: + raise DataError(e) + + def get_tag_list(self): + self._tag_list = [] + # Step 1 + self._get_instance_attribute_list_service() + + # Step 2 + self._isolating_user_tag() + + # Step 3 + for tag in self._tag_list: + if tag['tag_type'] == 'struct': + tag['template'] = self._get_structure_makeup(tag['template_instance_id']) + + for idx, tag in enumerate(self._tag_list): + # print (tag) + if tag['tag_type'] == 'struct': + self._parse_udt_raw(tag) + + # Step 4 + + return self._tag_list + + + + diff --git a/python/pycomm_micro/ab_comm/clx.pyc b/python/pycomm_micro/ab_comm/clx.pyc new file mode 100644 index 0000000000000000000000000000000000000000..38ad70b039f65410dbd6712d82979084b3868051 GIT binary patch literal 24962 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 literal 0 HcmV?d00001 diff --git a/python/pycomm_micro/ab_comm/slc.py b/python/pycomm_micro/ab_comm/slc.py new file mode 100644 index 0000000..bfdb243 --- /dev/null +++ b/python/pycomm_micro/ab_comm/slc.py @@ -0,0 +1,446 @@ +# -*- coding: utf-8 -*- +# +# clx.py - Ethernet/IP Client for Rockwell PLCs +# +# +# Copyright (c) 2014 Agostino Ruscito +# +# Permission is hereby granted, free of charge, to any person obtaining a copy +# of this software and associated documentation files (the "Software"), to deal +# in the Software without restriction, including without limitation the rights +# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +# copies of the Software, and to permit persons to whom the Software is +# furnished to do so, subject to the following conditions: +# +# The above copyright notice and this permission notice shall be included in all +# copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +# SOFTWARE. +# +from pycomm_micro.cip.cip_base import * +from pycomm_micro.common import setup_logger +import re +import logging +import math + + +def parse_tag(tag): + t = re.search(r"(?P[CT])(?P\d{1,3})" + r"(:)(?P\d{1,3})" + r"(.)(?PACC|PRE|EN|DN|TT|CU|CD|DN|OV|UN|UA)", tag, flags=re.IGNORECASE) + if t: + if (1 <= int(t.group('file_number')) <= 255) \ + and (0 <= int(t.group('element_number')) <= 255): + return True, t.group(0), {'file_type': t.group('file_type').upper(), + 'file_number': t.group('file_number'), + 'element_number': t.group('element_number'), + 'sub_element': PCCC_CT[t.group('sub_element').upper()], + 'read_func': '\xa2', + 'write_func': '\xab', + 'address_field': 3} + + t = re.search(r"(?P[FBN])(?P\d{1,3})" + r"(:)(?P\d{1,3})" + r"(/(?P\d{1,2}))?", + tag, flags=re.IGNORECASE) + if t: + if t.group('sub_element') is not None: + if (1 <= int(t.group('file_number')) <= 255) \ + and (0 <= int(t.group('element_number')) <= 255) \ + and (0 <= int(t.group('sub_element')) <= 15): + + return True, t.group(0), {'file_type': t.group('file_type').upper(), + 'file_number': t.group('file_number'), + 'element_number': t.group('element_number'), + 'sub_element': t.group('sub_element'), + 'read_func': '\xa2', + 'write_func': '\xab', + 'address_field': 3} + else: + if (1 <= int(t.group('file_number')) <= 255) \ + and (0 <= int(t.group('element_number')) <= 255): + + return True, t.group(0), {'file_type': t.group('file_type').upper(), + 'file_number': t.group('file_number'), + 'element_number': t.group('element_number'), + 'sub_element': t.group('sub_element'), + 'read_func': '\xa2', + 'write_func': '\xab', + 'address_field': 2} + + t = re.search(r"(?P[IO])(:)(?P\d{1,3})" + r"(.)(?P\d{1,3})" + r"(/(?P\d{1,2}))?", tag, flags=re.IGNORECASE) + if t: + if t.group('sub_element') is not None: + if (0 <= int(t.group('file_number')) <= 255) \ + and (0 <= int(t.group('element_number')) <= 255) \ + and (0 <= int(t.group('sub_element')) <= 15): + + return True, t.group(0), {'file_type': t.group('file_type').upper(), + 'file_number': t.group('file_number'), + 'element_number': t.group('element_number'), + 'sub_element': t.group('sub_element'), + 'read_func': '\xa2', + 'write_func': '\xab', + 'address_field': 3} + else: + if (0 <= int(t.group('file_number')) <= 255) \ + and (0 <= int(t.group('element_number')) <= 255): + + return True, t.group(0), {'file_type': t.group('file_type').upper(), + 'file_number': t.group('file_number'), + 'element_number': t.group('element_number'), + 'read_func': '\xa2', + 'write_func': '\xab', + 'address_field': 2} + + t = re.search(r"(?PS)" + r"(:)(?P\d{1,3})" + r"(/(?P\d{1,2}))?", tag, flags=re.IGNORECASE) + if t: + if t.group('sub_element') is not None: + if (0 <= int(t.group('element_number')) <= 255) \ + and (0 <= int(t.group('sub_element')) <= 15): + return True, t.group(0), {'file_type': t.group('file_type').upper(), + 'file_number': '2', + 'element_number': t.group('element_number'), + 'sub_element': t.group('sub_element'), + 'read_func': '\xa2', + 'write_func': '\xab', + 'address_field': 3} + else: + if 0 <= int(t.group('element_number')) <= 255: + return True, t.group(0), {'file_type': t.group('file_type').upper(), + 'file_number': '2', + 'element_number': t.group('element_number'), + 'read_func': '\xa2', + 'write_func': '\xab', + 'address_field': 2} + + t = re.search(r"(?PB)(?P\d{1,3})" + r"(/)(?P\d{1,4})", + tag, flags=re.IGNORECASE) + if t: + if (1 <= int(t.group('file_number')) <= 255) \ + and (0 <= int(t.group('element_number')) <= 4095): + bit_position = int(t.group('element_number')) + element_number = bit_position / 16 + sub_element = bit_position - (element_number * 16) + return True, t.group(0), {'file_type': t.group('file_type').upper(), + 'file_number': t.group('file_number'), + 'element_number': element_number, + 'sub_element': sub_element, + 'read_func': '\xa2', + 'write_func': '\xab', + 'address_field': 3} + + return False, tag + + +class Driver(Base): + """ + SLC/PLC_5 Implementation + """ + def __init__(self, debug=False, filename=None): + if debug: + super(Driver, self).__init__(setup_logger('ab_comm.slc', logging.DEBUG, filename)) + else: + super(Driver, self).__init__(setup_logger('ab_comm.slc', logging.INFO, filename)) + + self.__version__ = '0.1' + self._last_sequence = 0 + + def _check_reply(self): + """ + check the replayed message for error + """ + self._more_packets_available = False + try: + if self._reply is None: + self._status = (3, '%s without reply' % REPLAY_INFO[unpack_dint(self._message[:2])]) + return False + # Get the type of command + typ = unpack_uint(self._reply[:2]) + + # Encapsulation status check + if unpack_dint(self._reply[8:12]) != SUCCESS: + self._status = (3, "{0} reply status:{1}".format(REPLAY_INFO[typ], + SERVICE_STATUS[unpack_dint(self._reply[8:12])])) + return False + + # Command Specific Status check + if typ == unpack_uint(ENCAPSULATION_COMMAND["send_rr_data"]): + status = unpack_usint(self._reply[42:43]) + if status != SUCCESS: + self._status = (3, "send_rr_data reply:{0} - Extend status:{1}".format( + SERVICE_STATUS[status], get_extended_status(self._reply, 42))) + return False + else: + return True + + elif typ == unpack_uint(ENCAPSULATION_COMMAND["send_unit_data"]): + status = unpack_usint(self._reply[48:49]) + if unpack_usint(self._reply[46:47]) == I_TAG_SERVICES_REPLY["Read Tag Fragmented"]: + self._parse_fragment(50, status) + return True + if unpack_usint(self._reply[46:47]) == I_TAG_SERVICES_REPLY["Get Instance Attributes List"]: + self._parse_tag_list(50, status) + return True + if status == 0x06: + self._status = (3, "Insufficient Packet Space") + self._more_packets_available = True + elif status != SUCCESS: + self._status = (3, "send_unit_data reply:{0} - Extend status:{1}".format( + SERVICE_STATUS[status], get_extended_status(self._reply, 48))) + return False + else: + return True + + return True + except Exception as e: + raise DataError(e) + + def read_tag(self, tag, n=1): + """ read tag from a connected plc + + Possible combination can be passed to this method: + print c.read_tag('F8:0', 3) return a list of 3 registers starting from F8:0 + print c.read_tag('F8:0') return one value + + It is possible to read status bit + + :return: None is returned in case of error + """ + res = parse_tag(tag) + if not res[0]: + self._status = (1000, "Error parsing the tag passed to read_tag({0},{1})".format(tag, n)) + self.logger.warning(self._status) + raise DataError("Error parsing the tag passed to read_tag({0},{1})".format(tag, n)) + + bit_read = False + bit_position = 0 + sub_element = 0 + if int(res[2]['address_field'] == 3): + bit_read = True + bit_position = int(res[2]['sub_element']) + + if not self._target_is_connected: + if not self.forward_open(): + self._status = (5, "Target did not connected. read_tag will not be executed.") + self.logger.warning(self._status) + raise Error("Target did not connected. read_tag will not be executed.") + + data_size = PCCC_DATA_SIZE[res[2]['file_type']] + + # Creating the Message Request Packet + self._last_sequence = pack_uint(Base._get_sequence()) + + message_request = [ + self._last_sequence, + '\x4b', + '\x02', + CLASS_ID["8-bit"], + PATH["PCCC"], + '\x07', + self.attribs['vid'], + self.attribs['vsn'], + '\x0f', + '\x00', + self._last_sequence[1], + self._last_sequence[0], + res[2]['read_func'], + pack_usint(data_size * n), + pack_usint(int(res[2]['file_number'])), + PCCC_DATA_TYPE[res[2]['file_type']], + pack_usint(int(res[2]['element_number'])), + pack_usint(sub_element) + ] + + self.logger.debug("SLC read_tag({0},{1})".format(tag, n)) + if self.send_unit_data( + build_common_packet_format( + DATA_ITEM['Connected'], + ''.join(message_request), + ADDRESS_ITEM['Connection Based'], + addr_data=self._target_cid,)): + sts = int(unpack_usint(self._reply[58])) + try: + if sts != 0: + sts_txt = PCCC_ERROR_CODE[sts] + self._status = (1000, "Error({0}) returned from read_tag({1},{2})".format(sts_txt, tag, n)) + self.logger.warning(self._status) + raise DataError("Error({0}) returned from read_tag({1},{2})".format(sts_txt, tag, n)) + + new_value = 61 + if bit_read: + if res[2]['file_type'] == 'T' or res[2]['file_type'] == 'C': + if bit_position == PCCC_CT['PRE']: + return UNPACK_PCCC_DATA_FUNCTION[res[2]['file_type']]( + self._reply[new_value+2:new_value+2+data_size]) + elif bit_position == PCCC_CT['ACC']: + return UNPACK_PCCC_DATA_FUNCTION[res[2]['file_type']]( + self._reply[new_value+4:new_value+4+data_size]) + + tag_value = UNPACK_PCCC_DATA_FUNCTION[res[2]['file_type']]( + self._reply[new_value:new_value+data_size]) + return get_bit(tag_value, bit_position) + + else: + values_list = [] + while len(self._reply[new_value:]) >= data_size: + values_list.append( + UNPACK_PCCC_DATA_FUNCTION[res[2]['file_type']](self._reply[new_value:new_value+data_size]) + ) + new_value = new_value+data_size + + if len(values_list) > 1: + return values_list + else: + return values_list[0] + + except Exception as e: + self._status = (1000, "Error({0}) parsing the data returned from read_tag({1},{2})".format(e, tag, n)) + self.logger.warning(self._status) + raise DataError("Error({0}) parsing the data returned from read_tag({1},{2})".format(e, tag, n)) + else: + raise DataError("send_unit_data returned not valid data") + + def write_tag(self, tag, value): + """ write tag from a connected plc + + Possible combination can be passed to this method: + c.write_tag('N7:0', [-30, 32767, -32767]) + c.write_tag('N7:0', 21) + c.read_tag('N7:0', 10) + + It is not possible to write status bit + + :return: None is returned in case of error + """ + res = parse_tag(tag) + if not res[0]: + self._status = (1000, "Error parsing the tag passed to read_tag({0},{1})".format(tag, value)) + self.logger.warning(self._status) + raise DataError("Error parsing the tag passed to read_tag({0},{1})".format(tag, value)) + + if isinstance(value, list) and int(res[2]['address_field'] == 3): + self._status = (1000, "Function's parameters error. read_tag({0},{1})".format(tag, value)) + self.logger.warning(self._status) + raise DataError("Function's parameters error. read_tag({0},{1})".format(tag, value)) + + if isinstance(value, list) and int(res[2]['address_field'] == 3): + self._status = (1000, "Function's parameters error. read_tag({0},{1})".format(tag, value)) + self.logger.warning(self._status) + raise DataError("Function's parameters error. read_tag({0},{1})".format(tag, value)) + + bit_field = False + bit_position = 0 + sub_element = 0 + if int(res[2]['address_field'] == 3): + bit_field = True + bit_position = int(res[2]['sub_element']) + values_list = '' + else: + values_list = '\xff\xff' + + multi_requests = False + if isinstance(value, list): + multi_requests = True + + if not self._target_is_connected: + if not self.forward_open(): + self._status = (1000, "Target did not connected. write_tag will not be executed.") + self.logger.warning(self._status) + raise Error("Target did not connected. write_tag will not be executed.") + + try: + n = 0 + if multi_requests: + data_size = PCCC_DATA_SIZE[res[2]['file_type']] + for v in value: + values_list += PACK_PCCC_DATA_FUNCTION[res[2]['file_type']](v) + n += 1 + else: + n = 1 + if bit_field: + data_size = 2 + + if (res[2]['file_type'] == 'T' or res[2]['file_type'] == 'C') \ + and (bit_position == PCCC_CT['PRE'] or bit_position == PCCC_CT['ACC']): + sub_element = bit_position + values_list = '\xff\xff' + PACK_PCCC_DATA_FUNCTION[res[2]['file_type']](value) + else: + sub_element = 0 + if value > 0: + values_list = pack_uint(math.pow(2, bit_position)) + pack_uint(math.pow(2, bit_position)) + else: + values_list = pack_uint(math.pow(2, bit_position)) + pack_uint(0) + + else: + values_list += PACK_PCCC_DATA_FUNCTION[res[2]['file_type']](value) + data_size = PCCC_DATA_SIZE[res[2]['file_type']] + + except Exception as e: + self._status = (1000, "Error({0}) packing the values to write to the" + "SLC write_tag({1},{2})".format(e, tag, value)) + self.logger.warning(self._status) + raise DataError("Error({0}) packing the values to write to the " + "SLC write_tag({1},{2})".format(e, tag, value)) + + data_to_write = values_list + + # Creating the Message Request Packet + self._last_sequence = pack_uint(Base._get_sequence()) + + message_request = [ + self._last_sequence, + '\x4b', + '\x02', + CLASS_ID["8-bit"], + PATH["PCCC"], + '\x07', + self.attribs['vid'], + self.attribs['vsn'], + '\x0f', + '\x00', + self._last_sequence[1], + self._last_sequence[0], + res[2]['write_func'], + pack_usint(data_size * n), + pack_usint(int(res[2]['file_number'])), + PCCC_DATA_TYPE[res[2]['file_type']], + pack_usint(int(res[2]['element_number'])), + pack_usint(sub_element) + ] + + self.logger.debug("SLC write_tag({0},{1})".format(tag, value)) + if self.send_unit_data( + build_common_packet_format( + DATA_ITEM['Connected'], + ''.join(message_request) + data_to_write, + ADDRESS_ITEM['Connection Based'], + addr_data=self._target_cid,)): + sts = int(unpack_usint(self._reply[58])) + try: + if sts != 0: + sts_txt = PCCC_ERROR_CODE[sts] + self._status = (1000, "Error({0}) returned from SLC write_tag({1},{2})".format(sts_txt, tag, value)) + self.logger.warning(self._status) + raise DataError("Error({0}) returned from SLC write_tag({1},{2})".format(sts_txt, tag, value)) + + return True + except Exception as e: + self._status = (1000, "Error({0}) parsing the data returned from " + "SLC write_tag({1},{2})".format(e, tag, value)) + self.logger.warning(self._status) + raise DataError("Error({0}) parsing the data returned from " + "SLC write_tag({1},{2})".format(e, tag, value)) + else: + raise DataError("send_unit_data returned not valid data") \ No newline at end of file diff --git a/python/pycomm_micro/ab_comm/slc.pyc b/python/pycomm_micro/ab_comm/slc.pyc new file mode 100644 index 0000000000000000000000000000000000000000..2500bf4b9ce2277a1b5eed65bb1b4996872265a3 GIT binary patch literal 12028 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 V$G2F`@L8M~06gWZp}|Dj`#s| zZ~0l*oA>sn#P2HpXI82iT8KNd5*N8(n^z3#Ldw>MR+eZkF+`g3GmMCmo%kcQY~h;B zm>^Oft?FM+X&_H3cug?a2gI4Xe%~A**=%-^R8O7Z!mNUGplk$0>upACI9oOxUC`5D JPGv!2i7y4OFg5@H literal 0 HcmV?d00001 diff --git a/python/pycomm_micro/cip/cip_base.py b/python/pycomm_micro/cip/cip_base.py new file mode 100644 index 0000000..36999e1 --- /dev/null +++ b/python/pycomm_micro/cip/cip_base.py @@ -0,0 +1,827 @@ +# -*- coding: utf-8 -*- +# +# cip_base.py - A set of classes methods and structures used to implement Ethernet/IP +# +# +# Copyright (c) 2014 Agostino Ruscito +# +# Permission is hereby granted, free of charge, to any person obtaining a copy +# of this software and associated documentation files (the "Software"), to deal +# in the Software without restriction, including without limitation the rights +# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +# copies of the Software, and to permit persons to whom the Software is +# furnished to do so, subject to the following conditions: +# +# The above copyright notice and this permission notice shall be included in all +# copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +# SOFTWARE. +# + +import struct +import socket + +from os import getpid +from pycomm_micro.cip.cip_const import * +from pycomm_micro.common import PycommError + + +class CommError(PycommError): + pass + + +class DataError(PycommError): + pass + + +def pack_sint(n): + return struct.pack('b', n) + + +def pack_usint(n): + return struct.pack('B', n) + + +def pack_int(n): + """pack 16 bit into 2 bytes little endian""" + return struct.pack('Ml+>sU&uCiR}D{ zQ{_~Wvh(GNb9tVpXJ!{5bu<;7mIkmrJ>5M$-EY7BzR&x1_agrXdxqY-9KBI7$xj~F zSMZ5mMl8UeV`|1+&f<=_>?l2FYB{r+H<$CqVY$4i^_gYH`b@3gYz~;q1IXz&;ec7g zBLn6gW8OCA&C7!(7)(=lWvRPNP~gqwf(eEc88X3cMRuEDSdn29>``Qo3HB)uQ)HhB_A9dA1Y?Sfnc#pT2TX8Kk%J~Uq{tx?j4Lv3g2Rd&Ho=4<6Q;Vy zglN@6<_s@Ka6yZX;)3=Z!vzg}7#B3^I4)?CYcD5oLAxHo1`A-^ zm#6Jz%3e;{%QLv3vCray=AFg`?Rw7UJ#Q~Bpsr)83oT?_xUeowepESWa^G?oO z{+MoGRMulAcuD0SH^Iv$m^HyGycG|FDAURVSxz}_?QNgLgb=+aiDvAJ1nwHmFsfcu&Bu7Vr=@PYIAM7t1#m@JO*N|Vf* z%pr)G?ECfdX6XCzkZv{`LA!=qTM9`=v1kpkQx~JK6`g99<5snD?doPFI5pd9G*=rp z-O1O(dTYyFYHv25IJIO8xECt3je2=~VTgLCe;?r>X;9b@KdRQ_ zr`SL?+c@{#$Mfo=w+jz@7Cn|!;y!!EU9HA0YHGNr z-PNr)jNDo^j%%SC)`M!fuFB7CJYYoNXov`C_;VQva`!Q~_f3M=A21S-7cC!v2A{}i zkpDn5cn)KqvcdE3&j!^8l?DN5@KQ#D`{Dyi@^+n+bnn`87c&F3+j+2dHv&i$wt6EC zGA?L^<(fpc#N2m}0@phfkp8!%>xlbWkLgguSiOE1PN1e0eWnnBO2eRC%_Ukb$e9Uf zHeI0Ua^_mjw15nu>|h6!CaKgGykUe@d`y+9Z!(V{W`Gx3?J&k<=EWR?DDDj=!nPfI)z?;uqo6fE!rT_4}AC}y0=oTS`typK8Q@zMIyqCuoom+ z+d-n3!fdV9DDPA~4~7UP7`gi4)9Dj;L#Es0Q9`F(87on{JvdsbOJ>(dqxy$WqkB)c z>z0=~GtBtx_4Q>j~i6$1h@C}+@ddNb&;AU@N4KWuyY(9*$8|)O@W^(ef zoL^j=SEMwzu&h?{N+EiY5ekTl%3{)Nn&%a#=ao0F81v@Syz_4^7gfQ^qBpBTvsr9D zi(O=0mS${Cl~q*9OfrKkOjdMR;f%tP!dDeeEA$jDDx8ze%28vhHL}kmf?>Vwn46Dt zh-a@HH@9;fW(+b!3!b}`GjBWm`#4Hn-(yygnP62w9T1De`@yr%ymb2JZ5b`HSJoP> z&2lVyHClmKMK_z#x=n1yg71}$-i$asZp{`L-5;ukeoP_3(zst1Qx43{q*-+M9LnQ55i|5lrG^gOAdEIf9pV zvjN!iH;@!jpOFjf?*y*nPQjUQhLy4>x7QI{kO~=Fd>c1l2+Sfd2WOCD#!p~?EuYwA zrZFpLvv55?2Ey=TycL0DZGBU5N5kFf)XAs#a9>g{#@v>)mh@jrI4`N6m;Rk4Eeri) zn$Agsk~#;vOJbp|1ZLctFu^W!H7{m_?GB!uKz~Xr_5G#~9B^&OwEoCAj)Nt*(xt$F z-N;i5T~Z6Trb>xp=qQ5X%@}^qFmOw8Ic`U8sruQ_y;#3iZ``OyEdPVg+>YG4 z6s&@^+eDJ)UHh-a`ynuXl`rIFulucxkz~Fze4O&K65C`G3CAxYllJ1!0l{ zXL**fC@!~RaVBDDQhWf3VbV08fVkLRN9BTb>k_92fdMJJS zP3sYJ^VcXxb`rY_}f!<*vtu?|5v__Q$ZgUBXLOu0LY%M@8NGUk)fk+U40K z^!p$$d(735Bx(HiZd1o{b5{~lZtccoCZk6`g20^tijG4!F0Z>@c)cA)F&6MD*TQ&c z$i>fG?4}4X-Ut!5Md<9s6u;3bH=DSYTa9)-a9M&CHOo;H22-|zHMd;fazn0Rx^R%E zwbdK-Pz8ivrWAfgOQVu8x<6T^g052fMr7qrJPj0wGPtE|c=inM=eV4z{xI#i(@c|g zUzpU|n7aY`O;Kt)p`KJ8B+`E-V9UuD7%7mR%L}tan>)rdEq=jBFk1bS~2I; z()29w+iO0to?LiZRxQpKFBBJ+{kd6h4+?p##nbz#H5brXz4>aRTAySssuHw{_u^)= zU5l%JLfc3MkrfF=wVG1MXfb2~wRkxQe0#@ws`aQEguVsZ6w!FlN4M7F4b@ZGD7U1E z^+FU=ACg!3Sm*5#NF$LdVm+TNZZ$Q?uOih9j95+V4CD^x4&=sjBe{XxA!j(h#~H(C zUv9`5%@M^7<9DBf9Oqyf=11}a&SSX|M-ZsOvFFfo3^>lwUqt{#o(Mt64u}_M^ghlY zf-8FuZQQwsU{sD(;RVwQlTx6loqAnm^D0{(O)E;!wJL_(>b!yvE3@n2tOC-P2R2Pd`D^jzyzpSgEd6D`gpXs-qRIg{`n&vGY}kQxkJRZhdjN<@YOS zutzVdRJS-Sd9xpHHA9Fi@VaeE%E~R9lQ4G653mbB<>KmQ*l5Sn;=R$qL>})?{ZC?< zB07vBrhnKO#qSVQ@gZkV4l!JZtPf3zqv_53CioESlvQP7*m+W{M@>D}MT7v9v5PFC%DXe!5hG;Flh-H$`f)BJ~0%hBu4A&JIq7Rukq^6swwusuhz=Qh&20mDE^Ui^ABe@X1v^iO)xGNzuDy zCxR3eDD7jH5GlRHN$?mO1%V9q&y71{5Xt-`g-nAz-4#laj1tHW^v{nl%IF=DI^+Ek>EW@}@($2!^A4mWh=-jJQ(u@Hk*% zViAG=X%T4}*op-JF-vHBf11-IvBSUDNvc9wSWkRpFloz>*?NLk|G5hRuoFfN%i8;9oR)5CCr=D|l-UXLIt{nq~r^-Gb8x(MQwk1z29rwub>M z@s|`u$xGLNd`E%Z7Ij`fBTs|120y!_%(hBUu)BHKQ7PDp3RaL&nxu-3=q;o+i9&xH zN)dEh-l#_ITDxA6u81}o?ONcjhHe|^k4|MhC$*GFeW1T)t-NJ(%dTq7FlEA1?j4v? z?}9}b`0aW%mWs$U+K|j$lG>0d>;*ampb~5=yw$`z8i}&pa;t0a(-*i1XI-e_6>o02Xlt+l_$)MY_M$e0 zSG8_iE$^E98Fv!zce!!zr1ia?bg3I%D$*xTx|0Ih@00CbvDljcUO#hnw}P#A?3D%L zLh5?$jVh>wE_S_+X!WGa@Ch}!dK#Qyp*Sb70D!l=YBXcrmKNuI zK8jjs%#{}XPZf(x)AMt06s^CVeT%}{n%6+2H=IJw_hE5D^7>ySZxGWO@WH6n=M)LF zvor+Lh-^J1QiGiGl|rqq39LFRbr;S>fH}C6pybZczX)oP)ULN{o`ufOhA8ufZ8r88w+eSaQBH6}@p_hU#9&9v3nu8IEy3z4834L~kNn%Gv| z6QX*b$J_rhiAm}jOsR)JP6<|FB+@gZ>CfPX*aS{vIN^?W5!aHnov z2%1_!pOda z&kCMM7LL@3h{DuPm9D{ZS6hv1VSP&T*yEUaKhA)_SmQw*uL0BeNij%W*+J&`$sE+c zYW~EHn1UD1;`I3*jpt9HZ9l{DJdB8em>h%HJcx0ncVfiZi_tw`EkY^)&LB;fF+4(B z@Xn#NB8o!`+bSmjoMB|7U^VI+93$`rhq>R``a)-zK?|fK_b^OCh(C_E9I^@2eOm^k zU^HhPngHkMuh0pGIdoN+7XT0l7Y?}g-8Y7O&qpB@PNR& zAt+zDeirNl|Knb<59T}IxPyVUK3lvU1uo&Uf~IAD%aXu>UD~Q11+A)Fq?1qYJ2RFR zch9|Up$aFsJb+!CMxu*TT4CiaVS&wR-E!RaC433>*8y*ep5TXkhr%4zjFWu%B5L#g z3}3@R^~9f)(~?A3ZlWJfN~5yTu3wA9v35xkuMY$%fU$hw=2*P0VjcXT6$vni340== z)C)8;tiBcgB3WClNLbr0o{fOPkBh+voJWBQ3b67H026@K+3xfsR^NvNYGA2_w~-le zQHezs?0#6v151WlBMOxxlJ=zY-j~suUuUnWi;QDM7FkLMPQ0IFj6&Sw%=f;;fRA(< ztJp`l-s!!I*VCMi=mj3g4fhuYhW8GS-#HTO1tgBeH_=ydml$}_*5+!XCfu6&7CR=K z&85}j0m0nhNuLsYh23V6En=;&q&QXr8kH(m7-SHZX|-%OgQneBb_g_gTn5U3dgnqQ zw-|7DNqH5X8W~3Elh&%R;GXPbVVRoVq5elm_?Vn+p>!7kznSq)K?3JI%&!Ux>W4ga z9-n9wr7}!RGz?g5(Qr-?L1Cr<2Ll`<0=W!ij0mK&MaJ?)Fa^ioba?VI4&dR`o6b%` z^k2piLwkG@2jlGVNt~Cn$0xNo#Ak8V&>o+}c|3c35+@Ap@kt!wv&ScKgwP(J#9=*q zd=h8)?D0t)Ikd+oaj?)HpTzpIJwEwRdVKOodVKOI&l~ck$9Uq95gg7ts>pGo{9~Yu zg+ingKY4s4L|y~Eh|{gg1ax_ra0k)^i9S@>h;pIy(iFO0kR^D-2`BY)r6T52BWxifAe<}Ej^WF+BysJ*)@_ zCqU;P=8;bfEe7?B)x2jgf*80n7&DC48LZ-Ah|Zu&43I9P_OcHbymJf}onk$M5diJa z;M6M4tDXU^b*>Y09_L5dWDE^OuM46k+eb*>XQrN&hjccS3pH7=m(rD{@{^>Of0AVPHNAA9%UvN; zTY?R~pAfNS*RYxsV|a`oXsmIGDy^yrE{BF+D@QRbKJ1L3 zKbn@Qwo<__LU(jmnap`I&N7>gR+ubNMSl6Ma2quR&_P9;DFdl?I)d~BYGm~rS-o>!7ld*aQz6@CJWd-bkE=Z#{iHrRZMo1 zJR>hbbYWIPq(N4114J~7yssnZF&46XLon<6|2I=>)|yErw0g4}ZDm&w!eU7Kf+v!0 zu&^MV=Iga^t0$wF%u&o8aNVZwJ7`vd@??8c81kbo3AM_BWP5c^qLW=pV2H+t#V!2* zCS#8v=w;V1M{U>oy1GW<^OLz19>dMn-p+u%xKiH-V-2P#4E+-{^fP~q?Ju7eKgzq> znU@|r-OK(4A$E6gC_hD(}+>WN4{^8L}SZn4e$u&tVU)KD4LgKUJeK$DLJ z7Iemni#3~JywM2UjdG-|gR5bP9qF5JXK%vx4g<@j@3Jb^H<#wfxUhQ#`O@AQnpV43 zxGlqK`mrvWpk?~W#L@G(&1gc$&k5f45f5V(G)D+LybTfC!pVMqu!nKy>fA1ZwV*OK zY}vIanO7;Av5R*%s_{nZxr6DJ0FTI;gkpC2k`&l&v>(eCnWV^Q{WywQZ3dYL*gZiHsa;F33PEOu z1Nfh8*P#Ogt7gd>C#bi%2^x1uHtjAfE@5+P14tF$5m0t-l()3MCIqqZ? zH{}>BhWN9Hjuv+iLJYuFg;heVOM>l}XO`08p_Pnf*eK>g9)V+L?N55P-e?Ni+TA+h zH@ld>0VD<_co5AlF3e0Xl`hUtYrSn|@xq1ag<0>Hkt2@`J*@T%N_sa@ynlt(Ji=k4 z3^V-`ogw24p20`Q73`30lYzg6yk27{E6uT*2xk1u=rbpf2ZJ3pP-*4*7BC97-2$Vuje}z= z4&18HepW#s_Y!>4fOsXX8ALydX1McMvz-f|532E2IxRV?Y7Ojivw{#a64`58IM#&S z>v6SQOJ-kZ`lhlHrzX-_Xm>MkB1)lYW?GvHGd8oSGKnbDkqgMaeO?_V+? z+PbpiA<1-B>bL9$O6#re@R2OZjG4TD2Qr9>y10Wp*czS|aX@hc=}pWSH*iR#1#y>PP$9jq0f zjJi^Sa=hA=z|Q@HLpJuj!an~%MzQ`ocWQ7q19Uo661GlYVI0;M7UBU-xRrwUS`Wan zs%=TIEjss9dH_8*lDGXCZII&j?j#u}^^OAxVecxC(7QlFeRf@4CmGVcG=1rq{VK=o zJWmp6%({eIYTCKh8t9%zVUkAc;&gf_eyw^FV$dEiIf>a5VAfP>Z5lM>hQ#PmhPXo~ zQM8*9xz?!;|OmJ&1?%wyNcC;-tYw$_ac992@m$lhvN z{yriW8ntlDL>Q@*EHv2+Y<$zcx(UqLooERR!$-87C~iui6Ok115>`y{l(3Uf6wcoz z7#%0-fYHB^ENNZEsu@_bANxe?T9<9YZgUljXV_hgl{4#N>@J6*_*1=rW?1b)ldz)3 zhT%cmsSy>yf~MUJ5`7N6#sRf;pl3>P7blN_qqKzL%2| z0A>sp$guj(33~4WqXs(=utY!6)yVywMq*NraW1V-@T(yBviVzzj`pS zpJS{WAHqu^HE_E0bF@|;_MsKZqTUUj2aAU&E@38+OzI+Proqr!qn-` zQ`w}}R~BrW)dX~Wt+k^%=zaS{wsZCznm^r{@F7ZDW34mGfF#iYm~0KeL?FndH(IQn ziaz;)D-bz$9#-Ofp2-%3-3w)W%PyAI=|NwFk1su|>-`NRcz=_@FEaRB2;}wS%~ugr ze=ncR;n{GljDu2kk-rL~GIkeW(KcGw-tS}-cn{xzQqQa(yBUD?*u|L(7pf;&{WA!n zBM7>wk;qWcP=g{f^TQ!?S5^0VDH+mAM2a!t0U64#E_y4|-mJg4R9vuUbz97j7yz)5 zn?d@;g@vL$eE?VJ^lR7#m>%VlFJ{S`Tf~u$H|5eP+D5_mVF&3Q(9MHo`>+>!yCLN zUsFJ!a}hXVazXnM)lVE#_KG~1#?-H|^gm=kG3fmYgCc^C2SEC)dk}r>VC;!UK&|g? z$zPzfe-*7YgCkgKAIawt4ko{O_>l^d{FMH@JAj2he)74&ye)+T#Q5sQF_i1WC({a= zW2iOyBLue~8N0Otjw)taK@!n!l})sQ^aKTNKO`-*0+j=_0`IgRE71z96p8^_B8eqx z0crfA0O$$m1nlnVs%cNBCW!9665ab!(JeSd#_9`^Cif6 z+ZDC}^8S$eYB3lJmL|CZ5d9wnIsp8gMR$dpGw+n$#d0NEFCzA`8(2EX45zN{ko>@K znUqzv$u|ZN?B9njc<#g01+s8o1SzIJm@ZI1raq`J-!8}RBB^0Oa;)eBx&Ro-!`oda zSv{Ht64Ks(X0f9P(s^XZ&0^c_a{0sQvK7p#>z=P2XESB4btE&L4XI?FnP0?dTc&v1 zMYH!`nD$>8{5J-Iwf~*5_ZUzbdjEp~H)VVOlL3XC6?YPRgu32;LeM4nxW{7~nR+Mr z*yn9c`9~CeXOLj}x7@V?*_ngVdhL2zDzl96bZWEjuux=|TtT*YqSp>GZWCzNu@-Ml&9wc$ z&w-!~Zu+=>k?9a}JUJ_R6T#MV32m_;&jkz68OAXrtvo&g)HvpV(+|o3%Epl>dU5RG zn&o6Y3-+*tbLz$6cYSgEqD2H5c1=##&=s7eWmp*hNk9f}y^(k%JLc^gzu7B$B6@JmGyG1KF2^*-pGB-EAVSM#f5<@_ zLK94X9tPq#_Uqj_aF>w1b-FWfZ)<}1>F{Au3EG(>rtR6uP0aaJ;SC*%V+Sbw z&dH)qmiz&R?l`LL8Yb5(&J=NQuzTo|anl%m3B@fA+%b#~*0^zTisC98xBZUGaU)N0 z7Gx8S%96u^qF*&zKcqNB8V54^WFI zK9ZzWn48o^ltyRqpfyRdube1*T0`^>}oFxn=t zWCjbcAF!<#$1w}AAoj~*7@{NgE`sBS@c7nZ!hGR^BV ztp%k@AtVoVOO@u`r3&kn3)aCJL82Fy(2$IE(o>hj=~?3aOP4C@gubS2Y2lNGQ=skJ zOcN`7nXzn$Wf*gjr0%zy9emZh0oIAEM>)9iu0scKP)G17z(5?qw@67Fq)x&a^hSpQ z0;>U19gfWfkQJQ!Pz81a7^FwKslXngu!|9z8lp@Gc8X-2Jv|0CUJm_yp2VOh+JFT7 z%?Lmuhya6#kjdCTCMNrwAAlanFVKCw;VgSYy@2z1F2c?~gtGe#f0Ak6VDL{F{04)6 z#^5&@{0j!$N~3zY9$Kx~Wn=9S)P6JX->}fXW$-Nq?=kot2EWVTKQIuQmF)Z9ywyC> z6cMhaI`bS5Qp~79@bb9rfFi{{%I!E`L6d$9O^D7Sf`gp-!ihp&zXSFO0g|;4jXX z;=|v8 +# +# Permission is hereby granted, free of charge, to any person obtaining a copy +# of this software and associated documentation files (the "Software"), to deal +# in the Software without restriction, including without limitation the rights +# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +# copies of the Software, and to permit persons to whom the Software is +# furnished to do so, subject to the following conditions: +# +# The above copyright notice and this permission notice shall be included in all +# copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +# SOFTWARE. +# + +ELEMENT_ID = { + "8-bit": '\x28', + "16-bit": '\x29', + "32-bit": '\x2a' +} + +CLASS_ID = { + "8-bit": '\x20', + "16-bit": '\x21', +} + +INSTANCE_ID = { + "8-bit": '\x24', + "16-bit": '\x25' +} + +ATTRIBUTE_ID = { + "8-bit": '\x30', + "16-bit": '\x31' +} + +# Path are combined as: +# CLASS_ID + PATHS +# For example PCCC path is CLASS_ID["8-bit"]+PATH["PCCC"] -> 0x20, 0x67, 0x24, 0x01. +PATH = { + 'Connection Manager': '\x06\x24\x01', + 'Router': '\x02\x24\x01', + 'Backplane Data Type': '\x66\x24\x01', + 'PCCC': '\x67\x24\x01', + 'DHCP Channel A': '\xa6\x24\x01\x01\x2c\x01', + 'DHCP Channel B': '\xa6\x24\x01\x02\x2c\x01' +} + +ENCAPSULATION_COMMAND = { # Volume 2: 2-3.2 Command Field UINT 2 byte + "nop": '\x00\x00', + "list_targets": '\x01\x00', + "list_services": '\x04\x00', + "list_identity": '\x63\x00', + "list_interfaces": '\x64\x00', + "register_session": '\x65\x00', + "unregister_session": '\x66\x00', + "send_rr_data": '\x6F\x00', + "send_unit_data": '\x70\x00' +} + +""" +When a tag is created, an instance of the Symbol Object (Class ID 0x6B) is created +inside the controller. + +When a UDT is created, an instance of the Template object (Class ID 0x6C) is +created to hold information about the structure makeup. +""" +CLASS_CODE = { + "Message Router": '\x02', # Volume 1: 5-1 + "Symbol Object": '\x6b', + "Template Object": '\x6c', + "Connection Manager": '\x06' # Volume 1: 3-5 +} + +CONNECTION_MANAGER_INSTANCE = { + 'Open Request': '\x01', + 'Open Format Rejected': '\x02', + 'Open Resource Rejected': '\x03', + 'Open Other Rejected': '\x04', + 'Close Request': '\x05', + 'Close Format Request': '\x06', + 'Close Other Request': '\x07', + 'Connection Timeout': '\x08' +} + +TAG_SERVICES_REQUEST = { + "Read Tag": 0x4c, + "Read Tag Fragmented": 0x52, + "Write Tag": 0x4d, + "Write Tag Fragmented": 0x53, + "Read Modify Write Tag": 0x4e, + "Multiple Service Packet": 0x0a, + "Get Instance Attributes List": 0x55, + "Get Attributes": 0x03, + "Read Template": 0x4c, +} + +TAG_SERVICES_REPLY = { + 0xcc: "Read Tag", + 0xd2: "Read Tag Fragmented", + 0xcd: "Write Tag", + 0xd3: "Write Tag Fragmented", + 0xce: "Read Modify Write Tag", + 0x8a: "Multiple Service Packet", + 0xd5: "Get Instance Attributes List", + 0x83: "Get Attributes", + 0xcc: "Read Template" +} + + +I_TAG_SERVICES_REPLY = { + "Read Tag": 0xcc, + "Read Tag Fragmented": 0xd2, + "Write Tag": 0xcd, + "Write Tag Fragmented": 0xd3, + "Read Modify Write Tag": 0xce, + "Multiple Service Packet": 0x8a, + "Get Instance Attributes List": 0xd5, + "Get Attributes": 0x83, + "Read Template": 0xcc +} + + +""" +EtherNet/IP Encapsulation Error Codes + +Standard CIP Encapsulation Error returned in the cip message header +""" +STATUS = { + 0x0000: "Success", + 0x0001: "The sender issued an invalid or unsupported encapsulation command", + 0x0002: "Insufficient memory", + 0x0003: "Poorly formed or incorrect data in the data portion", + 0x0064: "An originator used an invalid session handle when sending an encapsulation message to the target", + 0x0065: "The target received a message of invalid length", + 0x0069: "Unsupported Protocol Version" +} + +""" +MSG Error Codes: + +The following error codes have been taken from: + +Rockwell Automation Publication +1756-RM003P-EN-P - December 2014 +""" +SERVICE_STATUS = { + 0x01: "Connection failure (see extended status)", + 0x02: "Insufficient resource", + 0x03: "Invalid value", + 0x04: "IOI syntax error. A syntax error was detected decoding the Request Path (see extended status)", + 0x05: "Destination unknown, class unsupported, instance \nundefined or structure element undefined (see extended status)", + 0x06: "Insufficient Packet Space", + 0x07: "Connection lost", + 0x08: "Service not supported", + 0x09: "Error in data segment or invalid attribute value", + 0x0A: "Attribute list error", + 0x0B: "State already exist", + 0x0C: "Object state conflict", + 0x0D: "Object already exist", + 0x0E: "Attribute not settable", + 0x0F: "Permission denied", + 0x10: "Device state conflict", + 0x11: "Reply data too large", + 0x12: "Fragmentation of a primitive value", + 0x13: "Insufficient command data", + 0x14: "Attribute not supported", + 0x15: "Too much data", + 0x1A: "Bridge request too large", + 0x1B: "Bridge response too large", + 0x1C: "Attribute list shortage", + 0x1D: "Invalid attribute list", + 0x1E: "Request service error", + 0x1F: "Connection related failure (see extended status)", + 0x22: "Invalid reply received", + 0x25: "Key segment error", + 0x26: "Invalid IOI error", + 0x27: "Unexpected attribute in list", + 0x28: "DeviceNet error - invalid member ID", + 0x29: "DeviceNet error - member not settable", + 0xD1: "Module not in run state", + 0xFB: "Message port not supported", + 0xFC: "Message unsupported data type", + 0xFD: "Message uninitialized", + 0xFE: "Message timeout", + 0xff: "General Error (see extended status)" +} + +EXTEND_CODES = { + 0x01: { + 0x0100: "Connection in use", + 0x0103: "Transport not supported", + 0x0106: "Ownership conflict", + 0x0107: "Connection not found", + 0x0108: "Invalid connection type", + 0x0109: "Invalid connection size", + 0x0110: "Module not configured", + 0x0111: "EPR not supported", + 0x0114: "Wrong module", + 0x0115: "Wrong device type", + 0x0116: "Wrong revision", + 0x0118: "Invalid configuration format", + 0x011A: "Application out of connections", + 0x0203: "Connection timeout", + 0x0204: "Unconnected message timeout", + 0x0205: "Unconnected send parameter error", + 0x0206: "Message too large", + 0x0301: "No buffer memory", + 0x0302: "Bandwidth not available", + 0x0303: "No screeners available", + 0x0305: "Signature match", + 0x0311: "Port not available", + 0x0312: "Link address not available", + 0x0315: "Invalid segment type", + 0x0317: "Connection not scheduled" + }, + 0x04: { + 0x0000: "Extended status out of memory", + 0x0001: "Extended status out of instances" + }, + 0x05: { + 0x0000: "Extended status out of memory", + 0x0001: "Extended status out of instances" + }, + 0x1F: { + 0x0203: "Connection timeout" + }, + 0xff: { + 0x7: "Wrong data type", + 0x2001: "Excessive IOI", + 0x2002: "Bad parameter value", + 0x2018: "Semaphore reject", + 0x201B: "Size too small", + 0x201C: "Invalid size", + 0x2100: "Privilege failure", + 0x2101: "Invalid keyswitch position", + 0x2102: "Password invalid", + 0x2103: "No password issued", + 0x2104: "Address out of range", + 0x2105: "Address and how many out of range", + 0x2106: "Data in use", + 0x2107: "Type is invalid or not supported", + 0x2108: "Controller in upload or download mode", + 0x2109: "Attempt to change number of array dimensions", + 0x210A: "Invalid symbol name", + 0x210B: "Symbol does not exist", + 0x210E: "Search failed", + 0x210F: "Task cannot start", + 0x2110: "Unable to write", + 0x2111: "Unable to read", + 0x2112: "Shared routine not editable", + 0x2113: "Controller in faulted mode", + 0x2114: "Run mode inhibited" + + } +} +DATA_ITEM = { + 'Connected': '\xb1\x00', + 'Unconnected': '\xb2\x00' +} + +ADDRESS_ITEM = { + 'Connection Based': '\xa1\x00', + 'Null': '\x00\x00', + 'UCMM': '\x00\x00' +} + +UCMM = { + 'Interface Handle': 0, + 'Item Count': 2, + 'Address Type ID': 0, + 'Address Length': 0, + 'Data Type ID': 0x00b2 +} + +CONNECTION_SIZE = { + 'Backplane': '\x03', # CLX + 'Direct Network': '\x02' +} + +HEADER_SIZE = 24 +EXTENDED_SYMBOL = '\x91' +BOOL_ONE = 0xff +REQUEST_SERVICE = 0 +REQUEST_PATH_SIZE = 1 +REQUEST_PATH = 2 +SUCCESS = 0 +INSUFFICIENT_PACKETS = 6 +OFFSET_MESSAGE_REQUEST = 40 + + +FORWARD_CLOSE = '\x4e' +UNCONNECTED_SEND = '\x52' +FORWARD_OPEN = '\x54' +LARGE_FORWARD_OPEN = '\x5b' +GET_CONNECTION_DATA = '\x56' +SEARCH_CONNECTION_DATA = '\x57' +GET_CONNECTION_OWNER = '\x5a' +MR_SERVICE_SIZE = 2 + +PADDING_BYTE = '\x00' +PRIORITY = '\x0a' +TIMEOUT_TICKS = '\x05' +TIMEOUT_MULTIPLIER = '\x01' +TRANSPORT_CLASS = '\xa3' + +CONNECTION_PARAMETER = { + 'PLC5': 0x4302, + 'SLC500': 0x4302, + 'CNET': 0x4320, + 'DHP': 0x4302, + 'Default': 0x43f8, +} + +""" +Atomic Data Type: + + Bit = Bool + Bit array = DWORD (32-bit boolean aray) + 8-bit integer = SINT +16-bit integer = UINT +32-bit integer = DINT + 32-bit float = REAL +64-bit integer = LINT + +From Rockwell Automation Publication 1756-PM020C-EN-P November 2012: +When reading a BOOL tag, the values returned for 0 and 1 are 0 and 0xff, respectively. +""" + +S_DATA_TYPE = { + 'BOOL': 0xc1, + 'SINT': 0xc2, # Signed 8-bit integer + 'INT': 0xc3, # Signed 16-bit integer + 'DINT': 0xc4, # Signed 32-bit integer + 'LINT': 0xc5, # Signed 64-bit integer + 'USINT': 0xc6, # Unsigned 8-bit integer + 'UINT': 0xc7, # Unsigned 16-bit integer + 'UDINT': 0xc8, # Unsigned 32-bit integer + 'ULINT': 0xc9, # Unsigned 64-bit integer + 'REAL': 0xca, # 32-bit floating point + 'LREAL': 0xcb, # 64-bit floating point + 'STIME': 0xcc, # Synchronous time + 'DATE': 0xcd, + 'TIME_OF_DAY': 0xce, + 'DATE_AND_TIME': 0xcf, + 'STRING': 0xd0, # character string (1 byte per character) + 'BYTE': 0xd1, # byte string 8-bits + 'WORD': 0xd2, # byte string 16-bits + 'DWORD': 0xd3, # byte string 32-bits + 'LWORD': 0xd4, # byte string 64-bits + 'STRING2': 0xd5, # character string (2 byte per character) + 'FTIME': 0xd6, # Duration high resolution + 'LTIME': 0xd7, # Duration long + 'ITIME': 0xd8, # Duration short + 'STRINGN': 0xd9, # character string (n byte per character) + 'SHORT_STRING': 0xda, # character string (1 byte per character, 1 byte length indicator) + 'TIME': 0xdb, # Duration in milliseconds + 'EPATH': 0xdc, # CIP Path segment + 'ENGUNIT': 0xdd, # Engineering Units + 'STRINGI': 0xde # International character string +} + +I_DATA_TYPE = { + 0xc1: 'BOOL', + 0xc2: 'SINT', # Signed 8-bit integer + 0xc3: 'INT', # Signed 16-bit integer + 0xc4: 'DINT', # Signed 32-bit integer + 0xc5: 'LINT', # Signed 64-bit integer + 0xc6: 'USINT', # Unsigned 8-bit integer + 0xc7: 'UINT', # Unsigned 16-bit integer + 0xc8: 'UDINT', # Unsigned 32-bit integer + 0xc9: 'ULINT', # Unsigned 64-bit integer + 0xca: 'REAL', # 32-bit floating point + 0xcb: 'LREAL', # 64-bit floating point + 0xcc: 'STIME', # Synchronous time + 0xcd: 'DATE', + 0xce: 'TIME_OF_DAY', + 0xcf: 'DATE_AND_TIME', + 0xd0: 'STRING', # character string (1 byte per character) + 0xd1: 'BYTE', # byte string 8-bits + 0xd2: 'WORD', # byte string 16-bits + 0xd3: 'DWORD', # byte string 32-bits + 0xd4: 'LWORD', # byte string 64-bits + 0xd5: 'STRING2', # character string (2 byte per character) + 0xd6: 'FTIME', # Duration high resolution + 0xd7: 'LTIME', # Duration long + 0xd8: 'ITIME', # Duration short + 0xd9: 'STRINGN', # character string (n byte per character) + 0xda: 'SHORT_STRING', # character string (1 byte per character, 1 byte length indicator) + 0xdb: 'TIME', # Duration in milliseconds + 0xdc: 'EPATH', # CIP Path segment + 0xdd: 'ENGUNIT', # Engineering Units + 0xde: 'STRINGI' # International character string +} + +REPLAY_INFO = { + 0x4e: 'FORWARD_CLOSE (4E,00)', + 0x52: 'UNCONNECTED_SEND (52,00)', + 0x54: 'FORWARD_OPEN (54,00)', + 0x6f: 'send_rr_data (6F,00)', + 0x70: 'send_unit_data (70,00)', + 0x00: 'nop', + 0x01: 'list_targets', + 0x04: 'list_services', + 0x63: 'list_identity', + 0x64: 'list_interfaces', + 0x65: 'register_session', + 0x66: 'unregister_session', +} + +PCCC_DATA_TYPE = { + 'N': '\x89', + 'B': '\x85', + 'T': '\x86', + 'C': '\x87', + 'S': '\x84', + 'F': '\x8a', + 'ST': '\x8d', + 'A': '\x8e', + 'R': '\x88', + 'O': '\x8b', + 'I': '\x8c' +} + +PCCC_DATA_SIZE = { + 'N': 2, + 'B': 2, + 'T': 6, + 'C': 6, + 'S': 2, + 'F': 4, + 'ST': 84, + 'A': 2, + 'R': 6, + 'O': 2, + 'I': 2 +} + +PCCC_CT = { + 'PRE': 1, + 'ACC': 2, + 'EN': 15, + 'TT': 14, + 'DN': 13, + 'CU': 15, + 'CD': 14, + 'OV': 12, + 'UN': 11, + 'UA': 10 +} + +PCCC_ERROR_CODE = { + -2: "Not Acknowledged (NAK)", + -3: "No Reponse, Check COM Settings", + -4: "Unknown Message from DataLink Layer", + -5: "Invalid Address", + -6: "Could Not Open Com Port", + -7: "No data specified to data link layer", + -8: "No data returned from PLC", + -20: "No Data Returned", + 16: "Illegal Command or Format, Address may not exist or not enough elements in data file", + 32: "PLC Has a Problem and Will Not Communicate", + 48: "Remote Node Host is Missing, Disconnected, or Shut Down", + 64: "Host Could Not Complete Function Due To Hardware Fault", + 80: "Addressing problem or Memory Protect Rungs", + 96: "Function not allows due to command protection selection", + 112: "Processor is in Program mode", + 128: "Compatibility mode file missing or communication zone problem", + 144: "Remote node cannot buffer command", + 240: "Error code in EXT STS Byte" +} \ No newline at end of file diff --git a/python/pycomm_micro/cip/cip_const.pyc b/python/pycomm_micro/cip/cip_const.pyc new file mode 100644 index 0000000000000000000000000000000000000000..130af936caee38ec4876a0c521f567b294e27ac5 GIT binary patch literal 10713 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 literal 0 HcmV?d00001 diff --git a/python/pycomm_micro/common.py b/python/pycomm_micro/common.py new file mode 100644 index 0000000..f44819c --- /dev/null +++ b/python/pycomm_micro/common.py @@ -0,0 +1,32 @@ +__author__ = 'Agostino Ruscito' +__version__ = "1.0.7" +__date__ = "08 03 2015" +import logging + + +logging.basicConfig( + filename="pycomm.log", + filemode='w', + level=logging.INFO, + format="%(name)-13s %(levelname)-10s %(asctime)s %(message)s", + # propagate=0, +) + +LOGGER = logging.getLogger('pycomm') + + +class PycommError(Exception): + pass + + +def setup_logger(name, level, filename=None): + log = logging.getLogger('pycomm.'+name) + log.setLevel(level) + if filename: + fh = logging.FileHandler(filename, mode='w') + fh.setFormatter(logging.Formatter("%(levelname)-10s %(asctime)s %(message)s")) + log.addHandler(fh) + log.propagate = False + + return log + diff --git a/python/pycomm_micro/common.pyc b/python/pycomm_micro/common.pyc new file mode 100644 index 0000000000000000000000000000000000000000..727654649251c3963600e3f479aaca616c924e2b GIT binary patch literal 1306 zcmb_b%TC)s6ulFhJRp>+RjImaHc0M*BtR>=sH(PzKqV9^qgHA*mhnuCgX6J06ADsT z!O!&j`T^~^!)x0Gmgf3C&fNESf0h?t{eJx;q1&&8|0f)C4kC$0pn^t%WQ|5Ok{(H) za*swHDUT*T$pw;ik`0ngk}ao%W|8C)U6C&s1aq6N;02&%QcW7e-4nVdAn50)#V+Lv zT?4Vi{88|fGhp&L)y8Ip?u0XwWLBF6C^x$O?zUM3*MHyXZ*?~N8}G8e_=dI# 0: - for t in tags: - tagList.append({"id":int(t[0]), "name":t[1], "val":None, "lastVal":None}); - - try: - tux = TuxEIP(libpath="/usr/lib/libtuxeip.so") - sess = tux.OpenSession(PLC_IP_ADDRESS) - reg = tux.RegisterSession(sess) - conn = tux.ConnectPLCOverCNET(sess, LGX, 1, 100, 123, randint(0,9999), 123, 321, 100, 5000, 1, '01') - - while True: - for r in tagList: - r["val"] = tux.ReadLGXDataAsFloat(sess, conn, r['name'], 1)[0] - print("{0} - {1}".format(r["name"], r["val"])) - if not r["val"] == r["lastVal"]: - db = MySQLdb.connect(host="127.0.0.1",user="website",passwd="henrypump",db="SolarData") - cur = db.cursor() - aQuery = """INSERT INTO SolarData.values (tagID, val) VALUES ('%d', '%f');"""%(r["id"], float(r["val"])) - print(aQuery) - storeVal = cur.execute(aQuery) - db.commit() - db.close() - r["lastVal"] = r["val"] - - time.sleep(10) - except Exception as err: - print err - pass - - -if __name__ == '__main__': - main() From 24eca4232482e8bb3b57bee84cdb100486e56a4e Mon Sep 17 00:00:00 2001 From: Patrick McDonagh Date: Tue, 1 Mar 2016 13:05:05 -0600 Subject: [PATCH 2/2] Changed driver name --- python/micro800.py | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/python/micro800.py b/python/micro800.py index e476982..5807eaa 100644 --- a/python/micro800.py +++ b/python/micro800.py @@ -1,15 +1,15 @@ -from pycomm_micro.ab_comm.clx import Driver as ClxDriver +from pycomm_micro.ab_comm.clx import Driver as u800Driver import logging import sys def readMicroTag(addr, tag): logging.basicConfig( - filename="ClxDriver.log", + filename="u800Driver.log", format="%(levelname)-10s %(asctime)s %(message)s", level=logging.DEBUG ) - c = ClxDriver() + c = u800Driver() if c.open(addr): try: @@ -25,11 +25,11 @@ def readMicroTag(addr, tag): def getTagType(addr, tag): logging.basicConfig( - filename="ClxDriver.log", + filename="u800Driver.log", format="%(levelname)-10s %(asctime)s %(message)s", level=logging.DEBUG ) - c = ClxDriver() + c = u800Driver() if c.open(addr): try: @@ -43,11 +43,11 @@ def getTagType(addr, tag): def writeMicroTag(addr, tag, val): logging.basicConfig( - filename="ClxDriver.log", + filename="u800Driver.log", format="%(levelname)-10s %(asctime)s %(message)s", level=logging.DEBUG ) - c = ClxDriver() + c = u800Driver() if c.open(addr): try: @@ -66,11 +66,11 @@ def writeMicroTag(addr, tag, val): def readMicroTagList(addr, tList): logging.basicConfig( - filename="ClxDriver.log", + filename="u800Driver.log", format="%(levelname)-10s %(asctime)s %(message)s", level=logging.DEBUG ) - c = ClxDriver() + c = u800Driver() if c.open(addr): vals = []