Added Folders
Add all the driver folders
This commit is contained in:
21
vfdipp/config.txt
Normal file
21
vfdipp/config.txt
Normal file
@@ -0,0 +1,21 @@
|
||||
{
|
||||
|
||||
"driverFileName":"vfdipp.py",
|
||||
"deviceName":"vfdipp",
|
||||
"driverId":"0080",
|
||||
"releaseVersion":"2",
|
||||
"files": {
|
||||
"file1":"vfdipp.py",
|
||||
"file2":"micro800.py",
|
||||
"file3":"vfd_ipp_channels.p",
|
||||
"file4":"vfd_ipp_channels_setup.py",
|
||||
"file5":"pycomm_micro/__init__.py",
|
||||
"file6":"pycomm_micro/common.py",
|
||||
"file7":"pycomm_micro/ab_comm/__init__.py",
|
||||
"file8":"pycomm_micro/ab_comm/clx.py",
|
||||
"file9":"pycomm_micro/ab_comm/slc.py",
|
||||
"file10":"pycomm_micro/cip/__init__.py",
|
||||
"file11":"pycomm_micro/cip/cip_base.py",
|
||||
"file12":"pycomm_micro/cip/cip_const.py" }
|
||||
|
||||
}
|
||||
94
vfdipp/micro800.py
Normal file
94
vfdipp/micro800.py
Normal file
@@ -0,0 +1,94 @@
|
||||
from pycomm_micro.ab_comm.clx import Driver as u800Driver
|
||||
import logging
|
||||
import sys
|
||||
|
||||
|
||||
def readMicroTag(addr, tag):
|
||||
logging.basicConfig(
|
||||
filename="u800Driver.log",
|
||||
format="%(levelname)-10s %(asctime)s %(message)s",
|
||||
level=logging.DEBUG
|
||||
)
|
||||
c = u800Driver()
|
||||
|
||||
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="u800Driver.log",
|
||||
format="%(levelname)-10s %(asctime)s %(message)s",
|
||||
level=logging.DEBUG
|
||||
)
|
||||
c = u800Driver()
|
||||
|
||||
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="u800Driver.log",
|
||||
format="%(levelname)-10s %(asctime)s %(message)s",
|
||||
level=logging.DEBUG
|
||||
)
|
||||
c = u800Driver()
|
||||
|
||||
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="u800Driver.log",
|
||||
format="%(levelname)-10s %(asctime)s %(message)s",
|
||||
level=logging.DEBUG
|
||||
)
|
||||
c = u800Driver()
|
||||
|
||||
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.")
|
||||
1
vfdipp/pycomm_micro/__init__.py
Normal file
1
vfdipp/pycomm_micro/__init__.py
Normal file
@@ -0,0 +1 @@
|
||||
__author__ = 'agostino'
|
||||
BIN
vfdipp/pycomm_micro/__init__.pyc
Normal file
BIN
vfdipp/pycomm_micro/__init__.pyc
Normal file
Binary file not shown.
2
vfdipp/pycomm_micro/ab_comm/__init__.py
Normal file
2
vfdipp/pycomm_micro/ab_comm/__init__.py
Normal file
@@ -0,0 +1,2 @@
|
||||
__author__ = 'agostino'
|
||||
import logging
|
||||
BIN
vfdipp/pycomm_micro/ab_comm/__init__.pyc
Normal file
BIN
vfdipp/pycomm_micro/ab_comm/__init__.pyc
Normal file
Binary file not shown.
847
vfdipp/pycomm_micro/ab_comm/clx.py
Normal file
847
vfdipp/pycomm_micro/ab_comm/clx.py
Normal file
@@ -0,0 +1,847 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
#
|
||||
# clx.py - Ethernet/IP Client for Rockwell PLCs
|
||||
#
|
||||
#
|
||||
# Copyright (c) 2014 Agostino Ruscito <ruscito@gmail.com>
|
||||
#
|
||||
# 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
|
||||
|
||||
|
||||
|
||||
|
||||
BIN
vfdipp/pycomm_micro/ab_comm/clx.pyc
Normal file
BIN
vfdipp/pycomm_micro/ab_comm/clx.pyc
Normal file
Binary file not shown.
446
vfdipp/pycomm_micro/ab_comm/slc.py
Normal file
446
vfdipp/pycomm_micro/ab_comm/slc.py
Normal file
@@ -0,0 +1,446 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
#
|
||||
# clx.py - Ethernet/IP Client for Rockwell PLCs
|
||||
#
|
||||
#
|
||||
# Copyright (c) 2014 Agostino Ruscito <ruscito@gmail.com>
|
||||
#
|
||||
# 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<file_type>[CT])(?P<file_number>\d{1,3})"
|
||||
r"(:)(?P<element_number>\d{1,3})"
|
||||
r"(.)(?P<sub_element>ACC|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<file_type>[FBN])(?P<file_number>\d{1,3})"
|
||||
r"(:)(?P<element_number>\d{1,3})"
|
||||
r"(/(?P<sub_element>\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<file_type>[IO])(:)(?P<file_number>\d{1,3})"
|
||||
r"(.)(?P<element_number>\d{1,3})"
|
||||
r"(/(?P<sub_element>\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"(?P<file_type>S)"
|
||||
r"(:)(?P<element_number>\d{1,3})"
|
||||
r"(/(?P<sub_element>\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"(?P<file_type>B)(?P<file_number>\d{1,3})"
|
||||
r"(/)(?P<element_number>\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")
|
||||
BIN
vfdipp/pycomm_micro/ab_comm/slc.pyc
Normal file
BIN
vfdipp/pycomm_micro/ab_comm/slc.pyc
Normal file
Binary file not shown.
1
vfdipp/pycomm_micro/cip/__init__.py
Normal file
1
vfdipp/pycomm_micro/cip/__init__.py
Normal file
@@ -0,0 +1 @@
|
||||
__author__ = 'agostino'
|
||||
BIN
vfdipp/pycomm_micro/cip/__init__.pyc
Normal file
BIN
vfdipp/pycomm_micro/cip/__init__.pyc
Normal file
Binary file not shown.
827
vfdipp/pycomm_micro/cip/cip_base.py
Normal file
827
vfdipp/pycomm_micro/cip/cip_base.py
Normal file
@@ -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 <ruscito@gmail.com>
|
||||
#
|
||||
# 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('<h', n)
|
||||
|
||||
|
||||
def pack_uint(n):
|
||||
"""pack 16 bit into 2 bytes little endian"""
|
||||
# print("N: {0}".format(n))
|
||||
return struct.pack('<H', n)
|
||||
|
||||
|
||||
def pack_dint(n):
|
||||
"""pack 32 bit into 4 bytes little endian"""
|
||||
return struct.pack('<i', n)
|
||||
|
||||
|
||||
def pack_real(r):
|
||||
"""unpack 4 bytes little endian to int"""
|
||||
return struct.pack('<f', r)
|
||||
|
||||
|
||||
def pack_lint(l):
|
||||
"""unpack 4 bytes little endian to int"""
|
||||
return struct.unpack('<q', l)
|
||||
|
||||
|
||||
def unpack_bool(st):
|
||||
if int(struct.unpack('B', st[0])[0]) == 0:
|
||||
return 0
|
||||
return 1
|
||||
|
||||
|
||||
def unpack_sint(st):
|
||||
return int(struct.unpack('b', st[0])[0])
|
||||
|
||||
|
||||
def unpack_usint(st):
|
||||
return int(struct.unpack('B', st[0])[0])
|
||||
|
||||
|
||||
def unpack_int(st):
|
||||
"""unpack 2 bytes little endian to int"""
|
||||
return int(struct.unpack('<h', st[0:2])[0])
|
||||
|
||||
|
||||
def unpack_uint(st):
|
||||
"""unpack 2 bytes little endian to int"""
|
||||
return int(struct.unpack('<H', st[0:2])[0])
|
||||
|
||||
|
||||
def unpack_dint(st):
|
||||
"""unpack 4 bytes little endian to int"""
|
||||
return int(struct.unpack('<i', st[0:4])[0])
|
||||
|
||||
|
||||
def unpack_real(st):
|
||||
"""unpack 4 bytes little endian to int"""
|
||||
return float(struct.unpack('<f', st[0:4])[0])
|
||||
|
||||
def unpack_lreal(st):
|
||||
"""unpack 8 bytes little endian to int"""
|
||||
return float(struct.unpack('<f', st[0:8])[0])
|
||||
|
||||
def unpack_lint(st):
|
||||
"""unpack 4 bytes little endian to int"""
|
||||
return int(struct.unpack('<q', st[0:8])[0])
|
||||
|
||||
|
||||
def get_bit(value, idx):
|
||||
""":returns value of bit at position idx"""
|
||||
return (value & (1 << idx)) != 0
|
||||
|
||||
|
||||
PACK_DATA_FUNCTION = {
|
||||
'BOOL': pack_sint,
|
||||
'SINT': pack_sint, # Signed 8-bit integer
|
||||
'INT': pack_int, # Signed 16-bit integer
|
||||
'UINT': pack_uint, # Unsigned 16-bit integer
|
||||
'USINT': pack_usint, # Unsigned 8-bit integer
|
||||
'DINT': pack_dint, # Signed 32-bit integer
|
||||
'REAL': pack_real, # 32-bit floating point
|
||||
'LREAL': pack_real, # 32-bit floating point
|
||||
'LINT': pack_lint,
|
||||
'BYTE': pack_sint, # byte string 8-bits
|
||||
'WORD': pack_uint, # byte string 16-bits
|
||||
'DWORD': pack_dint, # byte string 32-bits
|
||||
'LWORD': pack_lint # byte string 64-bits
|
||||
}
|
||||
|
||||
|
||||
UNPACK_DATA_FUNCTION = {
|
||||
'BOOL': unpack_bool,
|
||||
'SINT': unpack_sint, # Signed 8-bit integer
|
||||
'INT': unpack_int, # Signed 16-bit integer
|
||||
'UINT': unpack_uint, # Unsigned 16-bit
|
||||
'USINT': unpack_usint, # Unsigned 8-bit integer
|
||||
'DINT': unpack_dint, # Signed 32-bit integer
|
||||
'UDINT': unpack_dint, # Signed 32-bit integer
|
||||
'REAL': unpack_real, # 32-bit floating point,
|
||||
'LREAL': unpack_lreal, # 32-bit floating point,
|
||||
'LINT': unpack_lint,
|
||||
'BYTE': unpack_sint, # byte string 8-bits
|
||||
'WORD': unpack_uint, # byte string 16-bits
|
||||
'DWORD': unpack_dint, # byte string 32-bits
|
||||
'LWORD': unpack_lint # byte string 64-bits
|
||||
}
|
||||
|
||||
|
||||
DATA_FUNCTION_SIZE = {
|
||||
'BOOL': 1,
|
||||
'SINT': 1, # Signed 8-bit integer
|
||||
'INT': 2, # Signed 16-bit integer
|
||||
'UINT': 2, # Unsigned 16-bit integer
|
||||
'DINT': 4, # Signed 32-bit integer
|
||||
'REAL': 4, # 32-bit floating point
|
||||
'LINT': 8,
|
||||
'BYTE': 1, # byte string 8-bits
|
||||
'WORD': 2, # byte string 16-bits
|
||||
'DWORD': 4, # byte string 32-bits
|
||||
'LWORD': 8 # byte string 64-bits
|
||||
}
|
||||
|
||||
UNPACK_PCCC_DATA_FUNCTION = {
|
||||
'N': unpack_int,
|
||||
'B': unpack_int,
|
||||
'T': unpack_int,
|
||||
'C': unpack_int,
|
||||
'S': unpack_int,
|
||||
'F': unpack_real,
|
||||
'A': unpack_sint,
|
||||
'R': unpack_dint,
|
||||
'O': unpack_int,
|
||||
'I': unpack_int
|
||||
}
|
||||
|
||||
PACK_PCCC_DATA_FUNCTION = {
|
||||
'N': pack_int,
|
||||
'B': pack_int,
|
||||
'T': pack_int,
|
||||
'C': pack_int,
|
||||
'S': pack_int,
|
||||
'F': pack_real,
|
||||
'A': pack_sint,
|
||||
'R': pack_dint,
|
||||
'O': pack_int,
|
||||
'I': pack_int
|
||||
}
|
||||
|
||||
|
||||
def print_bytes_line(msg):
|
||||
out = ''
|
||||
for ch in msg:
|
||||
out += "{:0>2x}".format(ord(ch))
|
||||
return out
|
||||
|
||||
|
||||
def print_bytes_msg(msg, info=''):
|
||||
out = info
|
||||
new_line = True
|
||||
line = 0
|
||||
column = 0
|
||||
for idx, ch in enumerate(msg):
|
||||
if new_line:
|
||||
out += "\n({:0>4d}) ".format(line * 10)
|
||||
new_line = False
|
||||
out += "{:0>2x} ".format(ord(ch))
|
||||
if column == 9:
|
||||
new_line = True
|
||||
column = 0
|
||||
line += 1
|
||||
else:
|
||||
column += 1
|
||||
return out
|
||||
|
||||
|
||||
def get_extended_status(msg, start):
|
||||
status = unpack_usint(msg[start:start+1])
|
||||
# send_rr_data
|
||||
# 42 General Status
|
||||
# 43 Size of additional status
|
||||
# 44..n additional status
|
||||
|
||||
# send_unit_data
|
||||
# 48 General Status
|
||||
# 49 Size of additional status
|
||||
# 50..n additional status
|
||||
extended_status_size = (unpack_usint(msg[start+1:start+2]))*2
|
||||
extended_status = 0
|
||||
if extended_status_size != 0:
|
||||
# There is an additional status
|
||||
if extended_status_size == 1:
|
||||
extended_status = unpack_usint(msg[start+2:start+3])
|
||||
elif extended_status_size == 2:
|
||||
extended_status = unpack_uint(msg[start+2:start+4])
|
||||
elif extended_status_size == 4:
|
||||
extended_status = unpack_dint(msg[start+2:start+6])
|
||||
else:
|
||||
return 'Extended Status Size Unknown'
|
||||
try:
|
||||
return '{0}'.format(EXTEND_CODES[status][extended_status])
|
||||
except LookupError:
|
||||
return "Extended Status info not present"
|
||||
|
||||
|
||||
def create_tag_rp(tag, multi_requests=False):
|
||||
""" Create tag Request Packet
|
||||
|
||||
It returns the request packed wrapped around the tag passed.
|
||||
If any error it returns none
|
||||
"""
|
||||
tags = tag.split('.')
|
||||
rp = []
|
||||
index = []
|
||||
for tag in tags:
|
||||
add_index = False
|
||||
# Check if is an array tag
|
||||
if tag.find('[') != -1:
|
||||
# Remove the last square bracket
|
||||
tag = tag[:len(tag)-1]
|
||||
# Isolate the value inside bracket
|
||||
inside_value = tag[tag.find('[')+1:]
|
||||
# Now split the inside value in case part of multidimensional array
|
||||
index = inside_value.split(',')
|
||||
# Flag the existence of one o more index
|
||||
add_index = True
|
||||
# Get only the tag part
|
||||
tag = tag[:tag.find('[')]
|
||||
tag_length = len(tag)
|
||||
|
||||
# Create the request path
|
||||
rp.append(EXTENDED_SYMBOL) # ANSI Ext. symbolic segment
|
||||
rp.append(chr(tag_length)) # Length of the tag
|
||||
|
||||
# Add the tag to the Request path
|
||||
for char in tag:
|
||||
rp.append(char)
|
||||
# Add pad byte because total length of Request path must be word-aligned
|
||||
if tag_length % 2:
|
||||
rp.append(PADDING_BYTE)
|
||||
# Add any index
|
||||
if add_index:
|
||||
for idx in index:
|
||||
val = int(idx)
|
||||
if val <= 0xff:
|
||||
rp.append(ELEMENT_ID["8-bit"])
|
||||
rp.append(pack_usint(val))
|
||||
elif val <= 0xffff:
|
||||
rp.append(ELEMENT_ID["16-bit"]+PADDING_BYTE)
|
||||
rp.append(pack_uint(val))
|
||||
elif val <= 0xfffffffff:
|
||||
rp.append(ELEMENT_ID["32-bit"]+PADDING_BYTE)
|
||||
rp.append(pack_dint(val))
|
||||
else:
|
||||
# Cannot create a valid request packet
|
||||
return None
|
||||
|
||||
# At this point the Request Path is completed,
|
||||
if multi_requests:
|
||||
request_path = chr(len(rp)/2) + ''.join(rp)
|
||||
else:
|
||||
request_path = ''.join(rp)
|
||||
return request_path
|
||||
|
||||
|
||||
def build_common_packet_format(message_type, message, addr_type, addr_data=None, timeout=10):
|
||||
""" build_common_packet_format
|
||||
|
||||
It creates the common part for a CIP message. Check Volume 2 (page 2.22) of CIP specification for reference
|
||||
"""
|
||||
msg = pack_dint(0) # Interface Handle: shall be 0 for CIP
|
||||
msg += pack_uint(timeout) # timeout
|
||||
msg += pack_uint(2) # Item count: should be at list 2 (Address and Data)
|
||||
msg += addr_type # Address Item Type ID
|
||||
|
||||
if addr_data is not None:
|
||||
msg += pack_uint(len(addr_data)) # Address Item Length
|
||||
msg += addr_data
|
||||
else:
|
||||
msg += pack_uint(0) # Address Item Length
|
||||
msg += message_type # Data Type ID
|
||||
msg += pack_uint(len(message)) # Data Item Length
|
||||
msg += message
|
||||
return msg
|
||||
|
||||
|
||||
def build_multiple_service(rp_list, sequence=None):
|
||||
|
||||
mr = []
|
||||
if sequence is not None:
|
||||
mr.append(pack_uint(sequence))
|
||||
|
||||
mr.append(chr(TAG_SERVICES_REQUEST["Multiple Service Packet"])) # the Request Service
|
||||
mr.append(pack_usint(2)) # the Request Path Size length in word
|
||||
mr.append(CLASS_ID["8-bit"])
|
||||
mr.append(CLASS_CODE["Message Router"])
|
||||
mr.append(INSTANCE_ID["8-bit"])
|
||||
mr.append(pack_usint(1)) # Instance 1
|
||||
mr.append(pack_uint(len(rp_list))) # Number of service contained in the request
|
||||
|
||||
# Offset calculation
|
||||
offset = (len(rp_list) * 2) + 2
|
||||
for index, rp in enumerate(rp_list):
|
||||
if index == 0:
|
||||
mr.append(pack_uint(offset)) # Starting offset
|
||||
else:
|
||||
mr.append(pack_uint(offset))
|
||||
offset += len(rp)
|
||||
|
||||
for rp in rp_list:
|
||||
mr.append(rp)
|
||||
return mr
|
||||
|
||||
|
||||
def parse_multiple_request(message, tags, typ):
|
||||
""" parse_multi_request
|
||||
This function should be used to parse the message replayed to a multi request service rapped around the
|
||||
send_unit_data message.
|
||||
|
||||
|
||||
:param message: the full message returned from the PLC
|
||||
:param tags: The list of tags to be read
|
||||
:param typ: to specify if multi request service READ or WRITE
|
||||
:return: a list of tuple in the format [ (tag name, value, data type), ( tag name, value, data type) ].
|
||||
In case of error the tuple will be (tag name, None, None)
|
||||
"""
|
||||
offset = 50
|
||||
position = 50
|
||||
number_of_service_replies = unpack_uint(message[offset:offset+2])
|
||||
tag_list = []
|
||||
for index in range(number_of_service_replies):
|
||||
position += 2
|
||||
start = offset + unpack_uint(message[position:position+2])
|
||||
general_status = unpack_usint(message[start+2:start+3])
|
||||
|
||||
if general_status == 0:
|
||||
if typ == "READ":
|
||||
data_type = unpack_uint(message[start+4:start+6])
|
||||
try:
|
||||
value_begin = start + 6
|
||||
value_end = value_begin + DATA_FUNCTION_SIZE[I_DATA_TYPE[data_type]]
|
||||
value = message[value_begin:value_end]
|
||||
tag_list.append((tags[index],
|
||||
UNPACK_DATA_FUNCTION[I_DATA_TYPE[data_type]](value),
|
||||
I_DATA_TYPE[data_type]))
|
||||
except LookupError:
|
||||
tag_list.append((tags[index], None, None))
|
||||
else:
|
||||
tag_list.append((tags[index] + ('GOOD',)))
|
||||
else:
|
||||
if typ == "READ":
|
||||
tag_list.append((tags[index], None, None))
|
||||
else:
|
||||
tag_list.append((tags[index] + ('BAD',)))
|
||||
return tag_list
|
||||
|
||||
|
||||
class Socket:
|
||||
|
||||
def __init__(self, timeout=5.0):
|
||||
self.sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
|
||||
self.sock.settimeout(timeout)
|
||||
self.sock.setsockopt(socket.SOL_SOCKET, socket.SO_KEEPALIVE, 1)
|
||||
|
||||
def connect(self, host, port):
|
||||
try:
|
||||
self.sock.connect((host, port))
|
||||
except socket.timeout:
|
||||
raise CommError("Socket timeout during connection.")
|
||||
|
||||
def send(self, msg, timeout=0):
|
||||
if timeout != 0:
|
||||
self.sock.settimeout(timeout)
|
||||
total_sent = 0
|
||||
while total_sent < len(msg):
|
||||
try:
|
||||
sent = self.sock.send(msg[total_sent:])
|
||||
if sent == 0:
|
||||
raise CommError("socket connection broken.")
|
||||
total_sent += sent
|
||||
except socket.error:
|
||||
raise CommError("socket connection broken.")
|
||||
return total_sent
|
||||
|
||||
def receive(self, timeout=0):
|
||||
if timeout != 0:
|
||||
self.sock.settimeout(timeout)
|
||||
msg_len = 28
|
||||
chunks = []
|
||||
bytes_recd = 0
|
||||
one_shot = True
|
||||
while bytes_recd < msg_len:
|
||||
try:
|
||||
chunk = self.sock.recv(min(msg_len - bytes_recd, 2048))
|
||||
if chunk == '':
|
||||
raise CommError("socket connection broken.")
|
||||
if one_shot:
|
||||
data_size = int(struct.unpack('<H', chunk[2:4])[0]) # Length
|
||||
msg_len = HEADER_SIZE + data_size
|
||||
one_shot = False
|
||||
|
||||
chunks.append(chunk)
|
||||
bytes_recd += len(chunk)
|
||||
except socket.error as e:
|
||||
raise CommError(e)
|
||||
return ''.join(chunks)
|
||||
|
||||
def close(self):
|
||||
self.sock.close()
|
||||
|
||||
|
||||
def parse_symbol_type(symbol):
|
||||
""" parse_symbol_type
|
||||
|
||||
It parse the symbol to Rockwell Spec
|
||||
:param symbol: the symbol associated to a tag
|
||||
:return: A tuple containing information about the tag
|
||||
"""
|
||||
pass
|
||||
|
||||
return None
|
||||
|
||||
|
||||
class Base(object):
|
||||
_sequence = 0
|
||||
|
||||
|
||||
def __init__(self, logging):
|
||||
if Base._sequence == 0:
|
||||
Base._sequence = getpid()
|
||||
else:
|
||||
Base._sequence = Base._get_sequence()
|
||||
|
||||
self.logger = logging
|
||||
self.__version__ = '0.1'
|
||||
self.__sock = None
|
||||
self._session = 0
|
||||
self._connection_opened = False
|
||||
self._reply = None
|
||||
self._message = None
|
||||
self._target_cid = None
|
||||
self._target_is_connected = False
|
||||
self._tag_list = []
|
||||
self._buffer = {}
|
||||
self._device_description = "Device Unknown"
|
||||
self._last_instance = 0
|
||||
self._byte_offset = 0
|
||||
self._last_position = 0
|
||||
self._more_packets_available = False
|
||||
self._last_tag_read = ()
|
||||
self._last_tag_write = ()
|
||||
self._status = (0, "")
|
||||
|
||||
# self.attribs = {'context': '_pycomm_', 'protocol version': 1, 'rpi': 5000, 'port': 0xAF12, 'timeout': 10,
|
||||
# 'backplane': 1, 'cpu slot': 0, 'option': 0, 'cid': '\x27\x04\x19\x71', 'csn': '\x27\x04',
|
||||
# 'vid': '\x09\x10', 'vsn': '\x09\x10\x19\x71', 'name': 'Base', 'ip address': None}
|
||||
self.attribs = {'context': '_pycomm_', 'protocol version': 1, 'rpi': 5000, 'port': 0xAF12, 'timeout': 10,
|
||||
'backplane': 0, 'cpu slot': 0, 'option': 0, 'cid': '\x27\x04\x19\x71', 'csn': '\x27\x04',
|
||||
'vid': '\x09\x10', 'vsn': '\x09\x10\x19\x71', 'name': 'Base', 'ip address': None}
|
||||
|
||||
def __len__(self):
|
||||
return len(self.attribs)
|
||||
|
||||
def __getitem__(self, key):
|
||||
return self.attribs[key]
|
||||
|
||||
def __setitem__(self, key, value):
|
||||
self.attribs[key] = value
|
||||
|
||||
def __delitem__(self, key):
|
||||
try:
|
||||
del self.attribs[key]
|
||||
except LookupError:
|
||||
pass
|
||||
|
||||
def __iter__(self):
|
||||
return iter(self.attribs)
|
||||
|
||||
def __contains__(self, item):
|
||||
return item in self.attribs
|
||||
|
||||
def _check_reply(self):
|
||||
raise Socket.ImplementationError("The method has not been implemented")
|
||||
|
||||
@staticmethod
|
||||
def _get_sequence():
|
||||
""" Increase and return the sequence used with connected messages
|
||||
|
||||
:return: The New sequence
|
||||
"""
|
||||
if Base._sequence < 65535:
|
||||
Base._sequence += 1
|
||||
else:
|
||||
Base._sequence = getpid()
|
||||
return Base._sequence
|
||||
|
||||
def nop(self):
|
||||
""" No replay command
|
||||
|
||||
A NOP provides a way for either an originator or target to determine if the TCP connection is still open.
|
||||
"""
|
||||
self._message = self.build_header(ENCAPSULATION_COMMAND['nop'], 0)
|
||||
self._send()
|
||||
|
||||
def __repr__(self):
|
||||
return self._device_description
|
||||
|
||||
def description(self):
|
||||
return self._device_description
|
||||
|
||||
def list_identity(self):
|
||||
""" ListIdentity command to locate and identify potential target
|
||||
|
||||
return true if the replay contains the device description
|
||||
"""
|
||||
self._message = self.build_header(ENCAPSULATION_COMMAND['list_identity'], 0)
|
||||
self._send()
|
||||
self._receive()
|
||||
if self._check_reply():
|
||||
try:
|
||||
self._device_description = self._reply[63:-1]
|
||||
return True
|
||||
except Exception as e:
|
||||
raise CommError(e)
|
||||
return False
|
||||
|
||||
def send_rr_data(self, msg):
|
||||
""" SendRRData transfer an encapsulated request/reply packet between the originator and target
|
||||
|
||||
:param msg: The message to be send to the target
|
||||
:return: the replay received from the target
|
||||
"""
|
||||
self._message = self.build_header(ENCAPSULATION_COMMAND["send_rr_data"], len(msg))
|
||||
self._message += msg
|
||||
self._send()
|
||||
self._receive()
|
||||
return self._check_reply()
|
||||
|
||||
def send_unit_data(self, msg):
|
||||
""" SendUnitData send encapsulated connected messages.
|
||||
|
||||
:param msg: The message to be send to the target
|
||||
:return: the replay received from the target
|
||||
"""
|
||||
self._message = self.build_header(ENCAPSULATION_COMMAND["send_unit_data"], len(msg))
|
||||
self._message += msg
|
||||
self._send()
|
||||
self._receive()
|
||||
return self._check_reply()
|
||||
|
||||
def get_status(self):
|
||||
""" Get the last status/error
|
||||
|
||||
This method can be used after any call to get any details in case of error
|
||||
:return: A tuple containing (error group, error message)
|
||||
"""
|
||||
return self._status
|
||||
|
||||
def clear(self):
|
||||
""" Clear the last status/error
|
||||
|
||||
:return: return am empty tuple
|
||||
"""
|
||||
self._status = (0, "")
|
||||
|
||||
def build_header(self, command, length):
|
||||
""" Build the encapsulate message header
|
||||
|
||||
The header is 24 bytes fixed length, and includes the command and the length of the optional data portion.
|
||||
|
||||
:return: the headre
|
||||
"""
|
||||
try:
|
||||
h = command # Command UINT
|
||||
h += pack_uint(length) # Length UINT
|
||||
h += pack_dint(self._session) # Session Handle UDINT
|
||||
h += pack_dint(0) # Status UDINT
|
||||
h += self.attribs['context'] # Sender Context 8 bytes
|
||||
h += pack_dint(self.attribs['option']) # Option UDINT
|
||||
return h
|
||||
except Exception as e:
|
||||
raise CommError(e)
|
||||
|
||||
def register_session(self):
|
||||
""" Register a new session with the communication partner
|
||||
|
||||
:return: None if any error, otherwise return the session number
|
||||
"""
|
||||
if self._session:
|
||||
return self._session
|
||||
|
||||
self._session = 0
|
||||
self._message = self.build_header(ENCAPSULATION_COMMAND['register_session'], 4)
|
||||
self._message += pack_uint(self.attribs['protocol version'])
|
||||
self._message += pack_uint(0)
|
||||
self._send()
|
||||
self._receive()
|
||||
if self._check_reply():
|
||||
self._session = unpack_dint(self._reply[4:8])
|
||||
self.logger.debug("Session ={0} has been registered.".format(print_bytes_line(self._reply[4:8])))
|
||||
return self._session
|
||||
|
||||
self._status = 'Warning ! the session has not been registered.'
|
||||
self.logger.warning(self._status)
|
||||
return None
|
||||
|
||||
def forward_open(self):
|
||||
""" CIP implementation of the forward open message
|
||||
|
||||
Refer to ODVA documentation Volume 1 3-5.5.2
|
||||
|
||||
:return: False if any error in the replayed message
|
||||
"""
|
||||
if self._session == 0:
|
||||
self._status = (4, "A session need to be registered before to call forward_open.")
|
||||
raise CommError("A session need to be registered before to call forward open")
|
||||
|
||||
forward_open_msg = [
|
||||
FORWARD_OPEN,
|
||||
pack_usint(2),
|
||||
CLASS_ID["8-bit"],
|
||||
CLASS_CODE["Connection Manager"], # Volume 1: 5-1
|
||||
INSTANCE_ID["8-bit"],
|
||||
CONNECTION_MANAGER_INSTANCE['Open Request'],
|
||||
PRIORITY,
|
||||
TIMEOUT_TICKS,
|
||||
pack_dint(0),
|
||||
self.attribs['cid'],
|
||||
self.attribs['csn'],
|
||||
self.attribs['vid'],
|
||||
self.attribs['vsn'],
|
||||
TIMEOUT_MULTIPLIER,
|
||||
'\x00\x00\x00',
|
||||
pack_dint(self.attribs['rpi'] * 1000),
|
||||
pack_uint(CONNECTION_PARAMETER['Default']),
|
||||
pack_dint(self.attribs['rpi'] * 1000),
|
||||
pack_uint(CONNECTION_PARAMETER['Default']),
|
||||
TRANSPORT_CLASS, # Transport Class
|
||||
# CONNECTION_SIZE['Backplane'],
|
||||
CONNECTION_SIZE['Direct Network'],
|
||||
# pack_usint(self.attribs['backplane']),
|
||||
# pack_usint(self.attribs['cpu slot']),
|
||||
CLASS_ID["8-bit"],
|
||||
CLASS_CODE["Message Router"],
|
||||
INSTANCE_ID["8-bit"],
|
||||
pack_usint(1)
|
||||
]
|
||||
if self.send_rr_data(
|
||||
build_common_packet_format(DATA_ITEM['Unconnected'], ''.join(forward_open_msg), ADDRESS_ITEM['UCMM'],)):
|
||||
self._target_cid = self._reply[44:48]
|
||||
self._target_is_connected = True
|
||||
return True
|
||||
self._status = (4, "forward_open returned False")
|
||||
return False
|
||||
|
||||
def forward_close(self):
|
||||
""" CIP implementation of the forward close message
|
||||
|
||||
Each connection opened with the froward open message need to be closed.
|
||||
Refer to ODVA documentation Volume 1 3-5.5.3
|
||||
|
||||
:return: False if any error in the replayed message
|
||||
"""
|
||||
|
||||
if self._session == 0:
|
||||
self._status = (5, "A session need to be registered before to call forward_close.")
|
||||
raise CommError("A session need to be registered before to call forward_close.")
|
||||
# print ("Backplane:{0}\nCPU:{1}".format(self.attribs['backplane'], self.attribs['cpu slot']))
|
||||
forward_close_msg = [
|
||||
FORWARD_CLOSE,
|
||||
pack_usint(2),
|
||||
CLASS_ID["8-bit"],
|
||||
CLASS_CODE["Connection Manager"], # Volume 1: 5-1
|
||||
INSTANCE_ID["8-bit"],
|
||||
CONNECTION_MANAGER_INSTANCE['Open Request'],
|
||||
PRIORITY,
|
||||
TIMEOUT_TICKS,
|
||||
self.attribs['csn'],
|
||||
self.attribs['vid'],
|
||||
self.attribs['vsn'],
|
||||
CONNECTION_SIZE['Direct Network'],
|
||||
# CONNECTION_SIZE['Backplane'],
|
||||
'\x00', # Reserved
|
||||
# pack_usint(self.attribs['backplane']),
|
||||
# pack_usint(self.attribs['cpu slot']),
|
||||
CLASS_ID["8-bit"],
|
||||
CLASS_CODE["Message Router"],
|
||||
INSTANCE_ID["8-bit"],
|
||||
pack_usint(1)
|
||||
]
|
||||
if self.send_rr_data(
|
||||
build_common_packet_format(DATA_ITEM['Unconnected'], ''.join(forward_close_msg), ADDRESS_ITEM['UCMM'])):
|
||||
self._target_is_connected = False
|
||||
return True
|
||||
self._status = (5, "forward_close returned False")
|
||||
self.logger.warning(self._status)
|
||||
return False
|
||||
|
||||
def un_register_session(self):
|
||||
""" Un-register a connection
|
||||
|
||||
"""
|
||||
self._message = self.build_header(ENCAPSULATION_COMMAND['unregister_session'], 0)
|
||||
self._send()
|
||||
self._session = None
|
||||
|
||||
def _send(self):
|
||||
"""
|
||||
socket send
|
||||
:return: true if no error otherwise false
|
||||
"""
|
||||
try:
|
||||
self.logger.debug(print_bytes_msg(self._message, '-------------- SEND --------------'))
|
||||
self.__sock.send(self._message)
|
||||
except Exception as e:
|
||||
#self.clean_up()
|
||||
raise CommError(e)
|
||||
|
||||
def _receive(self):
|
||||
"""
|
||||
socket receive
|
||||
:return: true if no error otherwise false
|
||||
"""
|
||||
try:
|
||||
self._reply = self.__sock.receive()
|
||||
self.logger.debug(print_bytes_msg(self._reply, '----------- RECEIVE -----------'))
|
||||
except Exception as e:
|
||||
#self.clean_up()
|
||||
raise CommError(e)
|
||||
|
||||
def open(self, ip_address):
|
||||
"""
|
||||
socket open
|
||||
:return: true if no error otherwise false
|
||||
"""
|
||||
# handle the socket layer
|
||||
|
||||
if not self._connection_opened:
|
||||
try:
|
||||
if self.__sock is None:
|
||||
self.__sock = Socket()
|
||||
self.__sock.connect(ip_address, self.attribs['port'])
|
||||
self._connection_opened = True
|
||||
self.attribs['ip address'] = ip_address
|
||||
if self.register_session() is None:
|
||||
self._status = (13, "Session not registered")
|
||||
return False
|
||||
self.forward_close()
|
||||
return True
|
||||
except Exception as e:
|
||||
#self.clean_up()
|
||||
raise CommError(e)
|
||||
|
||||
def close(self):
|
||||
"""
|
||||
socket close
|
||||
:return: true if no error otherwise false
|
||||
"""
|
||||
try:
|
||||
if self._target_is_connected:
|
||||
self.forward_close()
|
||||
if self._session != 0:
|
||||
self.un_register_session()
|
||||
if self.__sock:
|
||||
self.__sock.close()
|
||||
except Exception as e:
|
||||
raise CommError(e)
|
||||
|
||||
self.clean_up()
|
||||
|
||||
def clean_up(self):
|
||||
self.__sock = None
|
||||
self._target_is_connected = False
|
||||
self._session = 0
|
||||
self._connection_opened = False
|
||||
|
||||
def is_connected(self):
|
||||
return self._connection_opened
|
||||
BIN
vfdipp/pycomm_micro/cip/cip_base.pyc
Normal file
BIN
vfdipp/pycomm_micro/cip/cip_base.pyc
Normal file
Binary file not shown.
482
vfdipp/pycomm_micro/cip/cip_const.py
Normal file
482
vfdipp/pycomm_micro/cip/cip_const.py
Normal file
@@ -0,0 +1,482 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
#
|
||||
# cip_const.py - A set of structures and constants used to implement the Ethernet/IP protocol
|
||||
#
|
||||
#
|
||||
# Copyright (c) 2014 Agostino Ruscito <ruscito@gmail.com>
|
||||
#
|
||||
# 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"
|
||||
}
|
||||
BIN
vfdipp/pycomm_micro/cip/cip_const.pyc
Normal file
BIN
vfdipp/pycomm_micro/cip/cip_const.pyc
Normal file
Binary file not shown.
32
vfdipp/pycomm_micro/common.py
Normal file
32
vfdipp/pycomm_micro/common.py
Normal file
@@ -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
|
||||
|
||||
BIN
vfdipp/pycomm_micro/common.pyc
Normal file
BIN
vfdipp/pycomm_micro/common.pyc
Normal file
Binary file not shown.
1362
vfdipp/vfd_ipp_channels.p
Normal file
1362
vfdipp/vfd_ipp_channels.p
Normal file
File diff suppressed because it is too large
Load Diff
698
vfdipp/vfd_ipp_channels_setup.py
Normal file
698
vfdipp/vfd_ipp_channels_setup.py
Normal file
@@ -0,0 +1,698 @@
|
||||
import pickle
|
||||
|
||||
channels = {
|
||||
'vfdnameplatehz':{
|
||||
'data_type':'REAL',
|
||||
'change_amount':0.5,
|
||||
'max_time_between_uploads':3600,
|
||||
'tag':'cfg_NameplateHz',
|
||||
'last_time_uploaded':0,
|
||||
'last_value':''
|
||||
},
|
||||
'modetest':{
|
||||
'data_type':'BOOL',
|
||||
'change_amount':None,
|
||||
'max_time_between_uploads':3600,
|
||||
'tag':'Test_Mode',
|
||||
'last_time_uploaded':0,
|
||||
'last_value':''
|
||||
},
|
||||
'alarmtemperature':{
|
||||
'data_type':'BOOL',
|
||||
'change_amount':None,
|
||||
'max_time_between_uploads':3600,
|
||||
'tag':'ALARM_Temperature',
|
||||
'last_time_uploaded':0,
|
||||
'last_value':''
|
||||
},
|
||||
'dhpressureshutdownlimit':{
|
||||
'data_type':'REAL',
|
||||
'change_amount':0.5,
|
||||
'max_time_between_uploads':3600,
|
||||
'tag':'DH_Pressure_Shutdown',
|
||||
'last_time_uploaded':0,
|
||||
'last_value':''
|
||||
},
|
||||
'dhtempstartuplimit':{
|
||||
'data_type':'REAL',
|
||||
'change_amount':0.5,
|
||||
'max_time_between_uploads':3600,
|
||||
'tag':'DH_Temp_Startup',
|
||||
'last_time_uploaded':0,
|
||||
'last_value':''
|
||||
},
|
||||
'vfdfaultcode':{
|
||||
'data_type':'UINT',
|
||||
'change_amount':0.5,
|
||||
'max_time_between_uploads':3600,
|
||||
'tag':'VFD_Fault_DriveFault_Code',
|
||||
'last_time_uploaded':0,
|
||||
'last_value':''
|
||||
},
|
||||
'dhdischargetemperature':{
|
||||
'data_type':'UINT',
|
||||
'change_amount':0.5,
|
||||
'max_time_between_uploads':3600,
|
||||
'tag':'DH_DischargeTemperature',
|
||||
'last_time_uploaded':0,
|
||||
'last_value':''
|
||||
},
|
||||
'dhmaxintakepressureforever':{
|
||||
'data_type':'UINT',
|
||||
'change_amount':0.5,
|
||||
'max_time_between_uploads':3600,
|
||||
'tag':'DH_MaxIntakePressure_Forever',
|
||||
'last_time_uploaded':0,
|
||||
'last_value':''
|
||||
},
|
||||
'alarmpressure':{
|
||||
'data_type':'BOOL',
|
||||
'change_amount':None,
|
||||
'max_time_between_uploads':3600,
|
||||
'tag':'ALARM_Pressure',
|
||||
'last_time_uploaded':0,
|
||||
'last_value':''
|
||||
},
|
||||
'dhtempstartupenabled':{
|
||||
'data_type':'BOOL',
|
||||
'change_amount':None,
|
||||
'max_time_between_uploads':3600,
|
||||
'tag':'DH_Temp_Startup_Enabled',
|
||||
'last_time_uploaded':0,
|
||||
'last_value':''
|
||||
},
|
||||
'vfdtorqueperfmode':{
|
||||
'data_type':'UINT',
|
||||
'change_amount':0.5,
|
||||
'max_time_between_uploads':3600,
|
||||
'tag':'cfg_TorquePerfMode',
|
||||
'last_time_uploaded':0,
|
||||
'last_value':''
|
||||
},
|
||||
'dhwindingtemperature':{
|
||||
'data_type':'UINT',
|
||||
'change_amount':0.5,
|
||||
'max_time_between_uploads':3600,
|
||||
'tag':'DH_WindingTemperature',
|
||||
'last_time_uploaded':0,
|
||||
'last_value':''
|
||||
},
|
||||
'vfdmotorpoles':{
|
||||
'data_type':'UINT',
|
||||
'change_amount':0.5,
|
||||
'max_time_between_uploads':3600,
|
||||
'tag':'cfg_MotorPoles',
|
||||
'last_time_uploaded':0,
|
||||
'last_value':''
|
||||
},
|
||||
'vfdnameplatehp':{
|
||||
'data_type':'REAL',
|
||||
'change_amount':0.5,
|
||||
'max_time_between_uploads':3600,
|
||||
'tag':'cfg_NameplateHP',
|
||||
'last_time_uploaded':0,
|
||||
'last_value':''
|
||||
},
|
||||
'vfdactive':{
|
||||
'data_type':'BOOL',
|
||||
'change_amount':None,
|
||||
'max_time_between_uploads':3600,
|
||||
'tag':'VFD_Active',
|
||||
'last_time_uploaded':0,
|
||||
'last_value':''
|
||||
},
|
||||
'rptubingpressure':{
|
||||
'data_type':'BOOL',
|
||||
'change_amount':None,
|
||||
'max_time_between_uploads':3600,
|
||||
'tag':'RP_TubingPressure',
|
||||
'last_time_uploaded':0,
|
||||
'last_value':''
|
||||
},
|
||||
'vfdspeedfdbk':{
|
||||
'data_type':'REAL',
|
||||
'change_amount':0.5,
|
||||
'max_time_between_uploads':3600,
|
||||
'tag':'VFD_SpeedFdbk',
|
||||
'last_time_uploaded':0,
|
||||
'last_value':''
|
||||
},
|
||||
'rpmode':{
|
||||
'data_type':'BOOL',
|
||||
'change_amount':None,
|
||||
'max_time_between_uploads':3600,
|
||||
'tag':'RP_Mode',
|
||||
'last_time_uploaded':0,
|
||||
'last_value':''
|
||||
},
|
||||
'dhmaxintaketemperatureforever':{
|
||||
'data_type':'REAL',
|
||||
'change_amount':0.5,
|
||||
'max_time_between_uploads':3600,
|
||||
'tag':'DH_MaxIntakeTemperature_Forever',
|
||||
'last_time_uploaded':0,
|
||||
'last_value':''
|
||||
},
|
||||
'spmode':{
|
||||
'data_type':'BOOL',
|
||||
'change_amount':None,
|
||||
'max_time_between_uploads':3600,
|
||||
'tag':'SP_Mode',
|
||||
'last_time_uploaded':0,
|
||||
'last_value':''
|
||||
},
|
||||
'vfdacceltime':{
|
||||
'data_type':'REAL',
|
||||
'change_amount':0.5,
|
||||
'max_time_between_uploads':3600,
|
||||
'tag':'VFD_AccelTime',
|
||||
'last_time_uploaded':0,
|
||||
'last_value':''
|
||||
},
|
||||
'tubingpressurealarmdelay':{
|
||||
'data_type':'UDINT',
|
||||
'change_amount':0.5,
|
||||
'max_time_between_uploads':3600,
|
||||
'tag':'TubingPressure_Alarm_Delay',
|
||||
'last_time_uploaded':0,
|
||||
'last_value':''
|
||||
},
|
||||
'dhtooltype':{
|
||||
'data_type':'UINT',
|
||||
'change_amount':0.5,
|
||||
'max_time_between_uploads':3600,
|
||||
'tag':'DH_ToolType',
|
||||
'last_time_uploaded':0,
|
||||
'last_value':''
|
||||
},
|
||||
'sppressure':{
|
||||
'data_type':'BOOL',
|
||||
'change_amount':None,
|
||||
'max_time_between_uploads':3600,
|
||||
'tag':'SP_Pressure',
|
||||
'last_time_uploaded':0,
|
||||
'last_value':''
|
||||
},
|
||||
'alarmmode':{
|
||||
'data_type':'BOOL',
|
||||
'change_amount':None,
|
||||
'max_time_between_uploads':3600,
|
||||
'tag':'ALARM_Mode',
|
||||
'last_time_uploaded':0,
|
||||
'last_value':''
|
||||
},
|
||||
'tubingpressureeumin':{
|
||||
'data_type':'REAL',
|
||||
'change_amount':0.5,
|
||||
'max_time_between_uploads':3600,
|
||||
'tag':'TubingPressure_EU_Min',
|
||||
'last_time_uploaded':0,
|
||||
'last_value':''
|
||||
},
|
||||
'vfdstopmode':{
|
||||
'data_type':'UINT',
|
||||
'change_amount':0.5,
|
||||
'max_time_between_uploads':3600,
|
||||
'tag':'cfg_StopMode',
|
||||
'last_time_uploaded':0,
|
||||
'last_value':''
|
||||
},
|
||||
'vfdready':{
|
||||
'data_type':'BOOL',
|
||||
'change_amount':None,
|
||||
'max_time_between_uploads':3600,
|
||||
'tag':'VFD_Ready',
|
||||
'last_time_uploaded':0,
|
||||
'last_value':''
|
||||
},
|
||||
'offmode':{
|
||||
'data_type':'BOOL',
|
||||
'change_amount':None,
|
||||
'max_time_between_uploads':3600,
|
||||
'tag':'Off_Mode',
|
||||
'last_time_uploaded':0,
|
||||
'last_value':''
|
||||
},
|
||||
'alarmtubingpressure':{
|
||||
'data_type':'BOOL',
|
||||
'change_amount':None,
|
||||
'max_time_between_uploads':3600,
|
||||
'tag':'ALARM_TubingPressure',
|
||||
'last_time_uploaded':0,
|
||||
'last_value':''
|
||||
},
|
||||
'vfddisabled':{
|
||||
'data_type':'BOOL',
|
||||
'change_amount':None,
|
||||
'max_time_between_uploads':3600,
|
||||
'tag':'VFD_Disabled',
|
||||
'last_time_uploaded':0,
|
||||
'last_value':''
|
||||
},
|
||||
'tubingpressureeumax':{
|
||||
'data_type':'REAL',
|
||||
'change_amount':0.5,
|
||||
'max_time_between_uploads':3600,
|
||||
'tag':'TubingPressure_EU_Max',
|
||||
'last_time_uploaded':0,
|
||||
'last_value':''
|
||||
},
|
||||
'vfdspeedref':{
|
||||
'data_type':'REAL',
|
||||
'change_amount':0.5,
|
||||
'max_time_between_uploads':3600,
|
||||
'tag':'VFD_SpeedRef',
|
||||
'last_time_uploaded':0,
|
||||
'last_value':''
|
||||
},
|
||||
'dhmaxintaketemperaturestartup':{
|
||||
'data_type':'REAL',
|
||||
'change_amount':0.5,
|
||||
'max_time_between_uploads':3600,
|
||||
'tag':'DH_MaxIntakeTemperature_Startup',
|
||||
'last_time_uploaded':0,
|
||||
'last_value':''
|
||||
},
|
||||
'sptemperature':{
|
||||
'data_type':'BOOL',
|
||||
'change_amount':None,
|
||||
'max_time_between_uploads':3600,
|
||||
'tag':'SP_Temperature',
|
||||
'last_time_uploaded':0,
|
||||
'last_value':''
|
||||
},
|
||||
'dhpressurestartup':{
|
||||
'data_type':'REAL',
|
||||
'change_amount':0.5,
|
||||
'max_time_between_uploads':3600,
|
||||
'tag':'DH_Pressure_Startup',
|
||||
'last_time_uploaded':0,
|
||||
'last_value':''
|
||||
},
|
||||
'handmode':{
|
||||
'data_type':'BOOL',
|
||||
'change_amount':None,
|
||||
'max_time_between_uploads':3600,
|
||||
'tag':'Hand_Mode',
|
||||
'last_time_uploaded':0,
|
||||
'last_value':''
|
||||
},
|
||||
'tubingpressurelo':{
|
||||
'data_type':'BOOL',
|
||||
'change_amount':None,
|
||||
'max_time_between_uploads':3600,
|
||||
'tag':'TubingPressure_Lo',
|
||||
'last_time_uploaded':0,
|
||||
'last_value':''
|
||||
},
|
||||
'runpermissive':{
|
||||
'data_type':'BOOL',
|
||||
'change_amount':None,
|
||||
'max_time_between_uploads':3600,
|
||||
'tag':'Run_Permissive',
|
||||
'last_time_uploaded':0,
|
||||
'last_value':''
|
||||
},
|
||||
'vfdmaxfreq':{
|
||||
'data_type':'REAL',
|
||||
'change_amount':0.5,
|
||||
'max_time_between_uploads':3600,
|
||||
'tag':'cfg_MaxFreq',
|
||||
'last_time_uploaded':0,
|
||||
'last_value':''
|
||||
},
|
||||
'dhtempshutdownenabled':{
|
||||
'data_type':'BOOL',
|
||||
'change_amount':None,
|
||||
'max_time_between_uploads':3600,
|
||||
'tag':'DH_Temp_Shutdown_Enabled',
|
||||
'last_time_uploaded':0,
|
||||
'last_value':''
|
||||
},
|
||||
'dhpressureshutdownenabled':{
|
||||
'data_type':'BOOL',
|
||||
'change_amount':None,
|
||||
'max_time_between_uploads':3600,
|
||||
'tag':'DH_Pressure_Shutdown_Enabled',
|
||||
'last_time_uploaded':0,
|
||||
'last_value':''
|
||||
},
|
||||
'dhintakepressure':{
|
||||
'data_type':'REAL',
|
||||
'change_amount':0.5,
|
||||
'max_time_between_uploads':3600,
|
||||
'tag':'DH_IntakePressure',
|
||||
'last_time_uploaded':0,
|
||||
'last_value':''
|
||||
},
|
||||
'rppressure':{
|
||||
'data_type':'BOOL',
|
||||
'change_amount':None,
|
||||
'max_time_between_uploads':3600,
|
||||
'tag':'RP_Pressure',
|
||||
'last_time_uploaded':0,
|
||||
'last_value':''
|
||||
},
|
||||
'rpremote':{
|
||||
'data_type':'BOOL',
|
||||
'change_amount':None,
|
||||
'max_time_between_uploads':3600,
|
||||
'tag':'RP_Remote',
|
||||
'last_time_uploaded':0,
|
||||
'last_value':''
|
||||
},
|
||||
'vfdnameplatefla':{
|
||||
'data_type':'REAL',
|
||||
'change_amount':0.5,
|
||||
'max_time_between_uploads':3600,
|
||||
'tag':'cfg_NameplateFLA',
|
||||
'last_time_uploaded':0,
|
||||
'last_value':''
|
||||
},
|
||||
'vfdfaultcommerror':{
|
||||
'data_type':'BOOL',
|
||||
'change_amount':None,
|
||||
'max_time_between_uploads':3600,
|
||||
'tag':'VFD_Fault_CommError',
|
||||
'last_time_uploaded':0,
|
||||
'last_value':''
|
||||
},
|
||||
'vfdfault':{
|
||||
'data_type':'BOOL',
|
||||
'change_amount':None,
|
||||
'max_time_between_uploads':3600,
|
||||
'tag':'VFD_Fault',
|
||||
'last_time_uploaded':0,
|
||||
'last_value':''
|
||||
},
|
||||
'dhpsirating':{
|
||||
'data_type':'UINT',
|
||||
'change_amount':0.5,
|
||||
'max_time_between_uploads':3600,
|
||||
'tag':'DH_PSIRating',
|
||||
'last_time_uploaded':0,
|
||||
'last_value':''
|
||||
},
|
||||
'vfdatspeedref':{
|
||||
'data_type':'BOOL',
|
||||
'change_amount':None,
|
||||
'max_time_between_uploads':3600,
|
||||
'tag':'VFD_AtSpeedRef',
|
||||
'last_time_uploaded':0,
|
||||
'last_value':''
|
||||
},
|
||||
'dhnumchannels':{
|
||||
'data_type':'UINT',
|
||||
'change_amount':0.5,
|
||||
'max_time_between_uploads':3600,
|
||||
'tag':'DH_NumChannels',
|
||||
'last_time_uploaded':0,
|
||||
'last_value':''
|
||||
},
|
||||
'vfddcbusvoltage':{
|
||||
'data_type':'REAL',
|
||||
'change_amount':5.0,
|
||||
'max_time_between_uploads':3600,
|
||||
'tag':'VFD_DCBusVoltage',
|
||||
'last_time_uploaded':0,
|
||||
'last_value':''
|
||||
},
|
||||
'tubingpressuretransducerenabled':{
|
||||
'data_type':'BOOL',
|
||||
'change_amount':None,
|
||||
'max_time_between_uploads':3600,
|
||||
'tag':'TubingPressure_Transducer_Enabled',
|
||||
'last_time_uploaded':0,
|
||||
'last_value':''
|
||||
},
|
||||
'dhdownholestatusint':{
|
||||
'data_type':'UINT',
|
||||
'change_amount':0.5,
|
||||
'max_time_between_uploads':3600,
|
||||
'tag':'DH_DownholeStatus_INT',
|
||||
'last_time_uploaded':0,
|
||||
'last_value':''
|
||||
},
|
||||
'dhtoolvoltage':{
|
||||
'data_type':'UINT',
|
||||
'change_amount':0.5,
|
||||
'max_time_between_uploads':3600,
|
||||
'tag':'DH_ToolVoltage',
|
||||
'last_time_uploaded':0,
|
||||
'last_value':''
|
||||
},
|
||||
'tubingpressurealarmstartupdelay':{
|
||||
'data_type':'DINT',
|
||||
'change_amount':0.5,
|
||||
'max_time_between_uploads':3600,
|
||||
'tag':'TubingPressure_Alarm_Startup_Delay',
|
||||
'last_time_uploaded':0,
|
||||
'last_value':''
|
||||
},
|
||||
'alarmvfd':{
|
||||
'data_type':'BOOL',
|
||||
'change_amount':None,
|
||||
'max_time_between_uploads':3600,
|
||||
'tag':'ALARM_VFD',
|
||||
'last_time_uploaded':0,
|
||||
'last_value':''
|
||||
},
|
||||
'automode':{
|
||||
'data_type':'BOOL',
|
||||
'change_amount':None,
|
||||
'max_time_between_uploads':3600,
|
||||
'tag':'Auto_Mode',
|
||||
'last_time_uploaded':0,
|
||||
'last_value':''
|
||||
},
|
||||
'dhfluidlevel':{
|
||||
'data_type':'REAL',
|
||||
'change_amount':0.5,
|
||||
'max_time_between_uploads':3600,
|
||||
'tag':'DH_Fluid_Level',
|
||||
'last_time_uploaded':0,
|
||||
'last_value':''
|
||||
},
|
||||
'alarmremote':{
|
||||
'data_type':'BOOL',
|
||||
'change_amount':None,
|
||||
'max_time_between_uploads':3600,
|
||||
'tag':'ALARM_Remote',
|
||||
'last_time_uploaded':0,
|
||||
'last_value':''
|
||||
},
|
||||
'spremote':{
|
||||
'data_type':'BOOL',
|
||||
'change_amount':None,
|
||||
'max_time_between_uploads':3600,
|
||||
'tag':'SP_Remote',
|
||||
'last_time_uploaded':0,
|
||||
'last_value':''
|
||||
},
|
||||
'spvfd':{
|
||||
'data_type':'BOOL',
|
||||
'change_amount':None,
|
||||
'max_time_between_uploads':3600,
|
||||
'tag':'SP_VFD',
|
||||
'last_time_uploaded':0,
|
||||
'last_value':''
|
||||
},
|
||||
'vfdnameplaterpm':{
|
||||
'data_type':'REAL',
|
||||
'change_amount':0.5,
|
||||
'max_time_between_uploads':3600,
|
||||
'tag':'cfg_NameplateRPM',
|
||||
'last_time_uploaded':0,
|
||||
'last_value':''
|
||||
},
|
||||
'vfdnameplateolcurrent':{
|
||||
'data_type':'REAL',
|
||||
'change_amount':0.5,
|
||||
'max_time_between_uploads':3600,
|
||||
'tag':'cfg_NameplateOLCurrent',
|
||||
'last_time_uploaded':0,
|
||||
'last_value':''
|
||||
},
|
||||
'startpermissive':{
|
||||
'data_type':'BOOL',
|
||||
'change_amount':None,
|
||||
'max_time_between_uploads':3600,
|
||||
'tag':'Start_Permissive',
|
||||
'last_time_uploaded':0,
|
||||
'last_value':''
|
||||
},
|
||||
'dhdischargepressure':{
|
||||
'data_type':'UINT',
|
||||
'change_amount':0.5,
|
||||
'max_time_between_uploads':3600,
|
||||
'tag':'DH_DischargePressure',
|
||||
'last_time_uploaded':0,
|
||||
'last_value':''
|
||||
},
|
||||
'tubingpressureok':{
|
||||
'data_type':'BOOL',
|
||||
'change_amount':None,
|
||||
'max_time_between_uploads':3600,
|
||||
'tag':'TubingPressure_OK',
|
||||
'last_time_uploaded':0,
|
||||
'last_value':''
|
||||
},
|
||||
'rptemperature':{
|
||||
'data_type':'BOOL',
|
||||
'change_amount':None,
|
||||
'max_time_between_uploads':3600,
|
||||
'tag':'RP_Temperature',
|
||||
'last_time_uploaded':0,
|
||||
'last_value':''
|
||||
},
|
||||
'vfdoutputcurrent':{
|
||||
'data_type':'REAL',
|
||||
'change_amount':0.5,
|
||||
'max_time_between_uploads':3600,
|
||||
'tag':'VFD_OutputCurrent',
|
||||
'last_time_uploaded':0,
|
||||
'last_value':''
|
||||
},
|
||||
'rpvfd':{
|
||||
'data_type':'BOOL',
|
||||
'change_amount':None,
|
||||
'max_time_between_uploads':3600,
|
||||
'tag':'RP_VFD',
|
||||
'last_time_uploaded':0,
|
||||
'last_value':''
|
||||
},
|
||||
'tubingpressurehisp':{
|
||||
'data_type':'REAL',
|
||||
'change_amount':0.5,
|
||||
'max_time_between_uploads':3600,
|
||||
'tag':'TubingPressure_Hi_SP',
|
||||
'last_time_uploaded':0,
|
||||
'last_value':''
|
||||
},
|
||||
'vfdoutputvoltage':{
|
||||
'data_type':'REAL',
|
||||
'change_amount':0.5,
|
||||
'max_time_between_uploads':3600,
|
||||
'tag':'VFD_OutputVoltage',
|
||||
'last_time_uploaded':0,
|
||||
'last_value':''
|
||||
},
|
||||
'downholetoolenabled':{
|
||||
'data_type':'BOOL',
|
||||
'change_amount':None,
|
||||
'max_time_between_uploads':3600,
|
||||
'tag':'Downhole_Tool_Enabled',
|
||||
'last_time_uploaded':0,
|
||||
'last_value':''
|
||||
},
|
||||
'vfdfaultdrivefault':{
|
||||
'data_type':'BOOL',
|
||||
'change_amount':None,
|
||||
'max_time_between_uploads':3600,
|
||||
'tag':'VFD_Fault_DriveFault',
|
||||
'last_time_uploaded':0,
|
||||
'last_value':''
|
||||
},
|
||||
'vfdnameplatevolts':{
|
||||
'data_type':'REAL',
|
||||
'change_amount':0.5,
|
||||
'max_time_between_uploads':3600,
|
||||
'tag':'cfg_NameplateVolts',
|
||||
'last_time_uploaded':0,
|
||||
'last_value':''
|
||||
},
|
||||
'dhintaketemperature':{
|
||||
'data_type':'REAL',
|
||||
'change_amount':0.5,
|
||||
'max_time_between_uploads':3600,
|
||||
'tag':'DH_IntakeTemperature',
|
||||
'last_time_uploaded':0,
|
||||
'last_value':''
|
||||
},
|
||||
'vfdminfreq':{
|
||||
'data_type':'REAL',
|
||||
'change_amount':0.5,
|
||||
'max_time_between_uploads':3600,
|
||||
'tag':'cfg_MinFreq',
|
||||
'last_time_uploaded':0,
|
||||
'last_value':''
|
||||
},
|
||||
'dhtempshutdown':{
|
||||
'data_type':'REAL',
|
||||
'change_amount':0.5,
|
||||
'max_time_between_uploads':3600,
|
||||
'tag':'DH_Temp_Shutdown',
|
||||
'last_time_uploaded':0,
|
||||
'last_value':''
|
||||
},
|
||||
'tubingpressurehi':{
|
||||
'data_type':'BOOL',
|
||||
'change_amount':None,
|
||||
'max_time_between_uploads':3600,
|
||||
'tag':'TubingPressure_Hi',
|
||||
'last_time_uploaded':0,
|
||||
'last_value':''
|
||||
},
|
||||
'vfddeceltime':{
|
||||
'data_type':'REAL',
|
||||
'change_amount':0.5,
|
||||
'max_time_between_uploads':3600,
|
||||
'tag':'VFD_DecelTime',
|
||||
'last_time_uploaded':0,
|
||||
'last_value':''
|
||||
},
|
||||
'dhpressurestartupenabled':{
|
||||
'data_type':'BOOL',
|
||||
'change_amount':None,
|
||||
'max_time_between_uploads':3600,
|
||||
'tag':'DH_Pressure_Startup_Enabled',
|
||||
'last_time_uploaded':0,
|
||||
'last_value':''
|
||||
},
|
||||
'dhmaxintakepressurestartup':{
|
||||
'data_type':'UINT',
|
||||
'change_amount':0.5,
|
||||
'max_time_between_uploads':3600,
|
||||
'tag':'DH_MaxIntakePressure_Startup',
|
||||
'last_time_uploaded':0,
|
||||
'last_value':''
|
||||
},
|
||||
'tubingpressurelosp':{
|
||||
'data_type':'REAL',
|
||||
'change_amount':0.5,
|
||||
'max_time_between_uploads':3600,
|
||||
'tag':'TubingPressure_Lo_SP',
|
||||
'last_time_uploaded':0,
|
||||
'last_value':''
|
||||
},
|
||||
'remoteshutdowndisabled':{
|
||||
'data_type':'BOOL',
|
||||
'change_amount':None,
|
||||
'max_time_between_uploads':3600,
|
||||
'tag':'Remote_Shutdown_Disabled',
|
||||
'last_time_uploaded':0,
|
||||
'last_value':''
|
||||
},'tubingpressure':{
|
||||
'data_type':'REAL',
|
||||
'change_amount':0.5,
|
||||
'max_time_between_uploads':3600,
|
||||
'tag':'TubingPressure_In',
|
||||
'last_time_uploaded':0,
|
||||
'last_value':''
|
||||
},'startcommand':{
|
||||
'data_type':'BOOL',
|
||||
'max_time_between_uploads':3600,
|
||||
'tag':'Start_Command',
|
||||
'last_time_uploaded':0,
|
||||
'last_value':''
|
||||
},'stopcommand':{
|
||||
'data_type':'BOOL',
|
||||
'max_time_between_uploads':3600,
|
||||
'tag':'Stop_Command',
|
||||
'last_time_uploaded':0,
|
||||
'last_value':''
|
||||
}
|
||||
}
|
||||
|
||||
with open('vfd_ipp_channels.p', 'wb') as ch_f:
|
||||
pickle.dump(channels, ch_f)
|
||||
323
vfdipp/vfdipp.py
Normal file
323
vfdipp/vfdipp.py
Normal file
@@ -0,0 +1,323 @@
|
||||
#!/usr/bin/python
|
||||
|
||||
import types
|
||||
import traceback
|
||||
import binascii
|
||||
import threading
|
||||
import time
|
||||
import thread
|
||||
import os
|
||||
import struct
|
||||
import sys
|
||||
import serial
|
||||
import minimalmodbus
|
||||
import pickle
|
||||
import re
|
||||
from device_base import deviceBase
|
||||
import micro800 as u800
|
||||
|
||||
import requests
|
||||
try:
|
||||
import json
|
||||
except:
|
||||
import simplejson as json
|
||||
|
||||
min_upload_time = 30
|
||||
addr = '192.168.1.20'
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
class start(threading.Thread, deviceBase):
|
||||
channels = {}
|
||||
def updateGPS(self):
|
||||
gps = self.mcu.gps
|
||||
print("GPS found me at {0}".format(gps))
|
||||
self.sendtodb("gps", gps, 0)
|
||||
|
||||
def setupChannels(self):
|
||||
with open('drivers/vfd_ipp_channels.p', 'rb') as ch_f:
|
||||
self.channels = pickle.load(ch_f)
|
||||
print("Channel List\n================")
|
||||
for x in self.channels.keys():
|
||||
print x
|
||||
print("================")
|
||||
|
||||
def __init__(self, name=None, number=None, mac=None, Q=None, mcu=None, companyId=None, offset=None, mqtt=None, Nodes=None):
|
||||
threading.Thread.__init__(self)
|
||||
deviceBase.__init__(self, name=name, number=number, mac=mac, Q=Q, mcu=mcu, companyId=companyId, offset=offset, mqtt=mqtt, Nodes=Nodes)
|
||||
|
||||
self.daemon = True
|
||||
self.version = "2"
|
||||
self.device_address = addr
|
||||
self.finished = threading.Event()
|
||||
threading.Thread.start(self)
|
||||
self.sendtodbJSON("device_address", self.device_address, 0)
|
||||
self.setupChannels()
|
||||
# self.run()
|
||||
|
||||
|
||||
# self.updateGPS()
|
||||
# this is a required function for all drivers, its goal is to upload some piece of data
|
||||
# about your device so it can be seen on the web
|
||||
def register(self):
|
||||
self.channels["status"]["last_value"] = ""
|
||||
|
||||
def run(self):
|
||||
print("****************\n*************\nEXECUTING RUN\n********************\n****************")
|
||||
self.runLoopStatus = ""
|
||||
last_OK_state = 0
|
||||
while True:
|
||||
if len(self.channels) > 0:
|
||||
try:
|
||||
for i in self.channels:
|
||||
runLoopStatus = i
|
||||
print("reading {0}".format(i))
|
||||
valData = u800.readMicroTag(self.device_address, self.channels[i]['tag'])
|
||||
print(valData)
|
||||
if valData:
|
||||
nowVal = valData[0]
|
||||
ch = self.channels[i]
|
||||
if ch['data_type'] == "BOOL":
|
||||
if ch['last_value'] == "":
|
||||
self.sendtodbJSON(i, nowVal, 0)
|
||||
ch['last_time_uploaded'] = time.time()
|
||||
ch['last_value'] = nowVal
|
||||
elif (not (ch['last_value'] == nowVal)) or ((time.time() - ch['last_time_uploaded']) > ch['max_time_between_uploads']):
|
||||
self.sendtodbJSON(i, nowVal, 0)
|
||||
ch['last_time_uploaded'] = time.time()
|
||||
ch['last_value'] = nowVal
|
||||
if (ch['data_type'] == "REAL") or (ch['data_type'][-3:] == "INT"):
|
||||
if ch['last_value'] == "":
|
||||
self.sendtodbJSON(i, nowVal, 0)
|
||||
ch['last_time_uploaded'] = time.time()
|
||||
ch['last_value'] = nowVal
|
||||
elif (abs(ch['last_value'] - nowVal) > ch['change_amount']) or ((time.time() - ch['last_time_uploaded']) > ch['max_time_between_uploads']):
|
||||
self.sendtodbJSON(i, nowVal, 0)
|
||||
ch['last_time_uploaded'] = time.time()
|
||||
ch['last_value'] = nowVal
|
||||
|
||||
runLoopStatus = "Complete"
|
||||
OK_state = 1
|
||||
if not OK_state == last_OK_state:
|
||||
self.sendtodbJSON("driver_ok", OK_state, 0)
|
||||
last_OK_state = OK_state
|
||||
time.sleep(10)
|
||||
except Exception, e:
|
||||
OK_state = 0
|
||||
if not OK_state == last_OK_state:
|
||||
self.sendtodbJSON("driver_ok", OK_state, 0)
|
||||
last_OK_state = OK_state
|
||||
sleep_timer = 30
|
||||
print "Error during {0} of run loop: {1}\nWill try again in {2} seconds...".format(runLoopStatus, e, sleep_timer)
|
||||
time.sleep(sleep_timer)
|
||||
else:
|
||||
print("Apparently no self.channels... length shows {0}".format(len(self.channels)))
|
||||
print self.channels
|
||||
self.setupChannels()
|
||||
time.sleep(30)
|
||||
def write_vfdconfig(self):
|
||||
print("Writing config to drive")
|
||||
return u800.writeMicroTag(addr, 'VFD_Write', 1)
|
||||
|
||||
def vfdipp_sync(self, name, value):
|
||||
self.sendtodb("connected", "true", 0)
|
||||
return True
|
||||
|
||||
def vfdipp_address(self, name, value):
|
||||
self.device_address = value
|
||||
return True
|
||||
|
||||
def vfdipp_gpsUpdate(self, name, value):
|
||||
updateGPS()
|
||||
return True
|
||||
|
||||
def vfdipp_dhpressurestartupenabled(self, name, value):
|
||||
print('trying to set DH_Pressure_Startup_Enabled to {0}'.format(value))
|
||||
return u800.writeMicroTag(addr, 'DH_Pressure_Startup_Enabled', int(value))
|
||||
|
||||
def vfdipp_vfddisabled(self, name, value):
|
||||
print('trying to set VFD_Disabled to {0}'.format(value))
|
||||
return u800.writeMicroTag(addr, 'VFD_Disabled', int(value))
|
||||
|
||||
def vfdipp_vfdnameplatefla(self, name, value):
|
||||
print('trying to set cfg_NameplateFLA to {0}'.format(value))
|
||||
if u800.writeMicroTag(addr, 'cfg_NameplateFLA', float(value)):
|
||||
return self.write_vfdconfig()
|
||||
else:
|
||||
return False
|
||||
|
||||
def vfdipp_dhpressurestartup(self, name, value):
|
||||
print('trying to set DH_Pressure_Startup to {0}'.format(value))
|
||||
return u800.writeMicroTag(addr, 'DH_Pressure_Startup', float(value))
|
||||
|
||||
def vfdipp_automode(self, name, value):
|
||||
print('trying to set Auto_Mode to {0}'.format(value))
|
||||
return u800.writeMicroTag(addr, 'Auto_Mode', int(value))
|
||||
|
||||
def vfdipp_vfdmotorpoles(self, name, value):
|
||||
print('trying to set cfg_MotorPoles to {0}'.format(value))
|
||||
if u800.writeMicroTag(addr, 'cfg_MotorPoles', int(value)):
|
||||
return self.write_vfdconfig()
|
||||
else:
|
||||
return False
|
||||
|
||||
def vfdipp_tubingpressurealarmdelay(self, name, value):
|
||||
print('trying to set TubingPressure_Alarm_Delay to {0}'.format(value))
|
||||
return u800.writeMicroTag(addr, 'TubingPressure_Alarm_Delay', int(value))
|
||||
|
||||
def vfdipp_dhtempshutdown(self, name, value):
|
||||
print('trying to set DH_Temp_Shutdown to {0}'.format(value))
|
||||
return u800.writeMicroTag(addr, 'DH_Temp_Shutdown', float(value))
|
||||
|
||||
def vfdipp_dhtempstartuplimit(self, name, value):
|
||||
print('trying to set DH_Temp_Startup to {0}'.format(value))
|
||||
return u800.writeMicroTag(addr, 'DH_Temp_Startup', float(value))
|
||||
|
||||
def vfdipp_vfdmaxfreq(self, name, value):
|
||||
print('trying to set cfg_MaxFreq to {0}'.format(value))
|
||||
if u800.writeMicroTag(addr, 'cfg_MaxFreq', float(value)):
|
||||
return self.write_vfdconfig()
|
||||
else:
|
||||
return False
|
||||
|
||||
def vfdipp_tubingpressurelosp(self, name, value):
|
||||
print('trying to set TubingPressure_Lo_SP to {0}'.format(value))
|
||||
return u800.writeMicroTag(addr, 'TubingPressure_Lo_SP', float(value))
|
||||
|
||||
def vfdipp_vfdacceltime(self, name, value):
|
||||
print('trying to set VFD_AccelTime to {0}'.format(value))
|
||||
return u800.writeMicroTag(addr, 'VFD_AccelTime', float(value))
|
||||
|
||||
def vfdipp_vfdnameplateolcurrent(self, name, value):
|
||||
print('trying to set cfg_NameplateOLCurrent to {0}'.format(value))
|
||||
if u800.writeMicroTag(addr, 'cfg_NameplateOLCurrent', float(value)):
|
||||
return self.write_vfdconfig()
|
||||
else:
|
||||
return False
|
||||
|
||||
def vfdipp_offmode(self, name, value):
|
||||
print('trying to set Off_Mode to {0}'.format(value))
|
||||
return u800.writeMicroTag(addr, 'Off_Mode', int(value))
|
||||
|
||||
def vfdipp_tubingpressurealarmstartupdelay(self, name, value):
|
||||
print('trying to set TubingPressure_Alarm_Startup_Delay to {0}'.format(value))
|
||||
return u800.writeMicroTag(addr, 'TubingPressure_Alarm_Startup_Delay', int(value))
|
||||
|
||||
def vfdipp_dhpressureshutdownenabled(self, name, value):
|
||||
print('trying to set DH_Pressure_Shutdown_Enabled to {0}'.format(value))
|
||||
return u800.writeMicroTag(addr, 'DH_Pressure_Shutdown_Enabled', int(value))
|
||||
|
||||
def vfdipp_dhtempshutdownenabled(self, name, value):
|
||||
print('trying to set DH_Temp_Shutdown_Enabled to {0}'.format(value))
|
||||
return u800.writeMicroTag(addr, 'DH_Temp_Shutdown_Enabled', int(value))
|
||||
|
||||
def vfdipp_tubingpressureeumin(self, name, value):
|
||||
print('trying to set TubingPressure_EU_Min to {0}'.format(value))
|
||||
return u800.writeMicroTag(addr, 'TubingPressure_EU_Min', float(value))
|
||||
|
||||
def vfdipp_vfddeceltime(self, name, value):
|
||||
print('trying to set VFD_DecelTime to {0}'.format(value))
|
||||
return u800.writeMicroTag(addr, 'VFD_DecelTime', float(value))
|
||||
|
||||
def vfdipp_vfdnameplaterpm(self, name, value):
|
||||
print('trying to set cfg_NameplateRPM to {0}'.format(value))
|
||||
if u800.writeMicroTag(addr, 'cfg_NameplateRPM', float(value)):
|
||||
return self.write_vfdconfig()
|
||||
else:
|
||||
return False
|
||||
|
||||
def vfdipp_vfdspeedref(self, name, value):
|
||||
print('trying to set VFD_SpeedRef to {0}'.format(value))
|
||||
return u800.writeMicroTag(addr, 'VFD_SpeedRef', float(value))
|
||||
|
||||
def vfdipp_tubingpressureeumax(self, name, value):
|
||||
print('trying to set TubingPressure_EU_Max to {0}'.format(value))
|
||||
return u800.writeMicroTag(addr, 'TubingPressure_EU_Max', float(value))
|
||||
|
||||
def vfdipp_handmode(self, name, value):
|
||||
print('trying to set Hand_Mode to {0}'.format(value))
|
||||
return u800.writeMicroTag(addr, 'Hand_Mode', int(value))
|
||||
|
||||
def vfdipp_tubingpressurehisp(self, name, value):
|
||||
print('trying to set TubingPressure_Hi_SP to {0}'.format(value))
|
||||
return u800.writeMicroTag(addr, 'TubingPressure_Hi_SP', float(value))
|
||||
|
||||
def vfdipp_remoteshutdowndisabled(self, name, value):
|
||||
print('trying to set Remote_Shutdown_Disabled to {0}'.format(value))
|
||||
return u800.writeMicroTag(addr, 'Remote_Shutdown_Disabled', int(value))
|
||||
|
||||
def vfdipp_dhpressureshutdownlimit(self, name, value):
|
||||
print('trying to set DH_Pressure_Shutdown to {0}'.format(value))
|
||||
return u800.writeMicroTag(addr, 'DH_Pressure_Shutdown', float(value))
|
||||
|
||||
def vfdipp_vfdnameplatevolts(self, name, value):
|
||||
print('trying to set cfg_NameplateVolts to {0}'.format(value))
|
||||
if u800.writeMicroTag(addr, 'cfg_NameplateVolts', float(value)):
|
||||
return self.write_vfdconfig()
|
||||
else:
|
||||
return False
|
||||
|
||||
def vfdipp_downholetoolenabled(self, name, value):
|
||||
print('trying to set Downhole_Tool_Enabled to {0}'.format(value))
|
||||
return u800.writeMicroTag(addr, 'Downhole_Tool_Enabled', int(value))
|
||||
|
||||
def vfdipp_vfdnameplatehz(self, name, value):
|
||||
print('trying to set cfg_NameplateHz to {0}'.format(value))
|
||||
if u800.writeMicroTag(addr, 'cfg_NameplateHz', float(value)):
|
||||
return self.write_vfdconfig()
|
||||
else:
|
||||
return False
|
||||
|
||||
def vfdipp_tubingpressuretransducerenabled(self, name, value):
|
||||
print('trying to set TubingPressure_Transducer_Enabled to {0}'.format(value))
|
||||
return u800.writeMicroTag(addr, 'TubingPressure_Transducer_Enabled', int(value))
|
||||
|
||||
def vfdipp_dhtempstartupenabled(self, name, value):
|
||||
print('trying to set DH_Temp_Startup_Enabled to {0}'.format(value))
|
||||
return u800.writeMicroTag(addr, 'DH_Temp_Startup_Enabled', int(value))
|
||||
|
||||
def vfdipp_vfdnameplatehp(self, name, value):
|
||||
print('trying to set cfg_NameplateHP to {0}'.format(value))
|
||||
if u800.writeMicroTag(addr, 'cfg_NameplateHP', float(value)):
|
||||
return self.write_vfdconfig()
|
||||
else:
|
||||
return False
|
||||
|
||||
def vfdipp_vfdminfreq(self, name, value):
|
||||
print('trying to set cfg_MinFreq to {0}'.format(value))
|
||||
if u800.writeMicroTag(addr, 'cfg_MinFreq', float(value)):
|
||||
return self.write_vfdconfig()
|
||||
else:
|
||||
return False
|
||||
|
||||
def vfdipp_startcommand(self, name, value):
|
||||
print('trying to set Start_Command to 1')
|
||||
if u800.writeMicroTag(addr, 'Start_Command', 1):
|
||||
print("Set Start_Command to 1")
|
||||
time.sleep(3)
|
||||
return u800.writeMicroTag(addr, 'Start_Command', 0)
|
||||
else:
|
||||
print("Couldn't set Start_Command to 1")
|
||||
return False
|
||||
|
||||
def vfdipp_stopcommand(self, name, value):
|
||||
print('trying to set Stop_Command to 1')
|
||||
if u800.writeMicroTag(addr, 'Stop_Command', 1):
|
||||
print("Set Stop_Command to 1")
|
||||
time.sleep(3)
|
||||
return u800.writeMicroTag(addr, 'Stop_Command', 0)
|
||||
else:
|
||||
print("Couldn't set Stop_Command to 1")
|
||||
return False
|
||||
|
||||
def vfdipp_vfdclearfault(self, name, value):
|
||||
print('trying to set VFD_ClearFault to 1')
|
||||
if u800.writeMicroTag(addr, 'VFD_ClearFault', 1):
|
||||
print("Set VFD_ClearFault to 1")
|
||||
time.sleep(3)
|
||||
return u800.writeMicroTag(addr, 'VFD_ClearFault', 0)
|
||||
else:
|
||||
print("Couldn't set VFD_ClearFault to 1")
|
||||
return False
|
||||
Reference in New Issue
Block a user