From 76f1b79aab3272242ec7943e64f817aa04dacb99 Mon Sep 17 00:00:00 2001 From: bobfox Date: Wed, 28 Apr 2021 10:03:53 -0700 Subject: [PATCH 01/10] Fixes for issues #31, #32, and #33 - Added experimental callback hooks for point read and write (#33). - Fixed scale factor 0 when writing as a float (#32). - Fixed timeout during discovery handling (#31). --- sunspec2/device.py | 42 ++++++++++++++++++++++++++++++++++----- sunspec2/modbus/client.py | 3 +++ 2 files changed, 40 insertions(+), 5 deletions(-) diff --git a/sunspec2/device.py b/sunspec2/device.py index 9624b6d..0459933 100644 --- a/sunspec2/device.py +++ b/sunspec2/device.py @@ -133,6 +133,11 @@ def __init__(self, pdef=None, model=None, group=None, model_offset=0, data=None, self.sf = None # scale factor point name self.sf_value = None # value of scale factor self.sf_required = False # point has a scale factor + self.read_func = None # function to be called on read + self.read_func_arg = None # the argument passed to the read_func + self.write_func = None # function to be called on write + self.write_func_arg = None # the argument passed to the write_func + if pdef: self.sf_required = (pdef.get(mdef.SF) is not None) if self.sf_required: @@ -189,6 +194,10 @@ def cvalue(self, v): self.set_value(v, computed=True, dirty=True) def get_value(self, computed=False): + # call read function, if set + if self.read_func: + self.read_func(self.model, self.read_func_arg) + v = self._value if computed and v is not None: if self.sf_required: @@ -227,16 +236,29 @@ def set_value(self, data=None, computed=False, dirty=None): (self.sf, self.pdef['name'])) else: raise ModelError('Scale factor %s for point %s not found' % (self.sf, self.pdef['name'])) - if self.sf_value: + if self.sf_value is not None: self._value = round(round(float(v), abs(self.sf_value)) / math.pow(10, self.sf_value)) else: self._value = v else: self._value = v + # call write function, if set + # should be used to set indication for subsequent processing rather than do detailed processing + if self.write_func: + self.write_func(self.model, self.write_func_arg) + + def set_read_func(self, func, arg=None): + self.read_func = func + self.read_func_arg = arg + + def set_write_func(self, func, arg=None): + self.write_func = func + self.write_func_arg = arg + def get_mb(self, computed=False): v = self._value - data = None + data = err = None if computed and v is not None: if self.sf_required: if self.sf_value is None: @@ -252,17 +274,27 @@ def get_mb(self, computed=False): sfv = self.sf_value if sfv: v = int(v * math.pow(10, sfv)) - data = self.info.to_data(v, (int(self.len) * 2)) + try: + data = self.info.to_data(v, (int(self.len) * 2)) + except Exception as e: + err = 'Error getting point value %s %s: %s' % (self.pdef[mdef.NAME], v, e) + if err: + raise ModelError(err) elif v is None: data = mb.create_unimpl_value(self.pdef[mdef.TYPE], len=(int(self.len) * 2)) if data is None: - data = self.info.to_data(v, (int(self.len) * 2)) + try: + data = self.info.to_data(v, (int(self.len) * 2)) + except Exception as e: + err = 'Error getting point value %s %s: %s' % (self.pdef[mdef.NAME], v, e) + if err: + raise ModelError(err) return data def set_mb(self, data=None, computed=False, dirty=None): + mb_len = self.len try: - mb_len = self.len # if not enough data, do not set but consume the data if len(data) < mb_len * 2: return len(data) diff --git a/sunspec2/modbus/client.py b/sunspec2/modbus/client.py index b8cfd66..87cc410 100644 --- a/sunspec2/modbus/client.py +++ b/sunspec2/modbus/client.py @@ -228,6 +228,9 @@ def scan(self, progress=None, delay=None, connect=True): except SunSpecModbusClientError as e: if not error: error = str(e) + except modbus_client.ModbusClientTimeout as e: + if not error: + error = str(e) except modbus_client.ModbusClientException: pass From 4405d92786733f1fed118be9c468eaaf97ca8b1d Mon Sep 17 00:00:00 2001 From: bobfox Date: Wed, 28 Apr 2021 14:28:26 -0700 Subject: [PATCH 02/10] Allow model 1 legacy length of 65 (# 30) --- sunspec2/device.py | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/sunspec2/device.py b/sunspec2/device.py index 0459933..b97fac9 100644 --- a/sunspec2/device.py +++ b/sunspec2/device.py @@ -342,12 +342,14 @@ def __init__(self, gdef=None, model=None, model_offset=0, group_len=0, data=None points = self.gdef.get(mdef.POINTS) if points: for pdef in points: - p = point_class(pdef, model=self.model, group=self, model_offset=model_offset, data=data, - data_offset=data_offset) - self.points_len += p.len - model_offset += p.len - data_offset += p.len - self.points[pdef[mdef.NAME]] = p + # allow legacy model 1 to have an alternate length of 65 registers + if self.len != 65 or model.model_id != 1 or pdef[mdef.NAME] != 'Pad': + p = point_class(pdef, model=self.model, group=self, model_offset=model_offset, data=data, + data_offset=data_offset) + self.points_len += p.len + model_offset += p.len + data_offset += p.len + self.points[pdef[mdef.NAME]] = p # initialize groups groups = self.gdef.get(mdef.GROUPS) if groups: From 25c9f7e6e4a55c50f3c98abb1d348df72cc60be9 Mon Sep 17 00:00:00 2001 From: shelcrow Date: Thu, 29 Apr 2021 09:51:03 -0700 Subject: [PATCH 03/10] #10 - Fixed issue so models are no longer stored under None if model def is missing --- sunspec2/device.py | 17 +++++++++-------- 1 file changed, 9 insertions(+), 8 deletions(-) diff --git a/sunspec2/device.py b/sunspec2/device.py index b97fac9..4e2defe 100644 --- a/sunspec2/device.py +++ b/sunspec2/device.py @@ -44,7 +44,7 @@ def get_model_info(model_id): for pdef in points: info = mb.point_type_info.get(pdef[mdef.TYPE]) plen = pdef.get(mdef.SIZE, None) - if plen is None: + if plen is not None: glen += info.len except: raise @@ -678,13 +678,14 @@ def add_model(self, model): model_list.append(model) # add by group id gname = model.gname - model_list = self.models.get(gname) - if model_list is None: - model_list = [] - self.models[gname] = model_list - model_list.append(model) - # add to model list - self.model_list.append(model) + if gname is not None: + model_list = self.models.get(gname) + if model_list is None: + model_list = [] + self.models[gname] = model_list + model_list.append(model) + # add to model list + self.model_list.append(model) model.device = self From c58e74d8aa384464e06c22ebfa2d7f6a4f4c93b6 Mon Sep 17 00:00:00 2001 From: shelcrow Date: Thu, 29 Apr 2021 12:32:42 -0700 Subject: [PATCH 04/10] #34 - Size attribute is now added when loading in from smdx --- sunspec2/mdef.py | 8 ++++++++ sunspec2/smdx.py | 6 ++++-- 2 files changed, 12 insertions(+), 2 deletions(-) diff --git a/sunspec2/mdef.py b/sunspec2/mdef.py index 116fcdc..926fc46 100644 --- a/sunspec2/mdef.py +++ b/sunspec2/mdef.py @@ -114,6 +114,14 @@ MODEL_DEF_EXT = '.json' +def get_size(ptype): + tinfo = point_type_info.get(ptype) + if tinfo is not None: + return tinfo.get('len') + else: + raise ModelDefinitionError('Unknown point type %s.' % ptype) + + def to_int(x): try: return int(x, 0) diff --git a/sunspec2/smdx.py b/sunspec2/smdx.py index 3eccba3..f942dad 100644 --- a/sunspec2/smdx.py +++ b/sunspec2/smdx.py @@ -188,10 +188,10 @@ def from_smdx(element): mdef.TYPE: mdef.TYPE_GROUP, mdef.POINTS: [ {mdef.NAME: 'ID', mdef.VALUE: mid, - mdef.DESCRIPTION: 'Model identifier', mdef.LABEL: 'Model ID', + mdef.DESCRIPTION: 'Model identifier', mdef.LABEL: 'Model ID', mdef.SIZE: 1, mdef.MANDATORY: mdef.MANDATORY_TRUE, mdef.STATIC: mdef.STATIC_TRUE, mdef.TYPE: mdef.TYPE_UINT16}, {mdef.NAME: 'L', - mdef.DESCRIPTION: 'Model length', mdef.LABEL: 'Model Length', + mdef.DESCRIPTION: 'Model length', mdef.LABEL: 'Model Length', mdef.SIZE: 1, mdef.MANDATORY: mdef.MANDATORY_TRUE, mdef.STATIC: mdef.STATIC_TRUE, mdef.TYPE: mdef.TYPE_UINT16} ] } @@ -316,6 +316,8 @@ def from_smdx_point(element): if plen is None: raise mdef.ModelDefinitionError('Missing len attribute for point: %s' % pid) point_def[mdef.SIZE] = plen + else: + point_def[mdef.SIZE] = mdef.get_size(ptype) mandatory = element.attrib.get(SMDX_ATTR_MANDATORY, SMDX_MANDATORY_FALSE) if mandatory not in smdx_mandatory_types: raise mdef.ModelDefinitionError('Unknown mandatory type: %s' % mandatory) From b8e091bb4099c719003b2ae5eb9693f14d1e003b Mon Sep 17 00:00:00 2001 From: shelcrow Date: Thu, 29 Apr 2021 13:14:35 -0700 Subject: [PATCH 05/10] Models updated to v2021-04-23 --- sunspec2/models | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sunspec2/models b/sunspec2/models index 2136f07..b521b41 160000 --- a/sunspec2/models +++ b/sunspec2/models @@ -1 +1 @@ -Subproject commit 2136f073c4779b47d21bae4e89cd8278aec8a681 +Subproject commit b521b4109d2d96fd7416adbfdf882bf0c348fb3e From 140b0356632b4efbc3a11789860e04c26aa3e1ff Mon Sep 17 00:00:00 2001 From: shelcrow Date: Fri, 30 Apr 2021 14:49:29 -0700 Subject: [PATCH 06/10] Updated version number --- setup.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/setup.py b/setup.py index 929e723..e53820b 100644 --- a/setup.py +++ b/setup.py @@ -9,7 +9,7 @@ setup( name='pysunspec2', - version='1.0.3', + version='1.0.4', description='Python SunSpec Tools', author='SunSpec Alliance', author_email='support@sunspec.org', From 6a12c6fa6237f125128963151d29b16e70b74904 Mon Sep 17 00:00:00 2001 From: shelcrow Date: Fri, 30 Apr 2021 14:50:24 -0700 Subject: [PATCH 07/10] Updated pytests --- sunspec2/tests/test_device.py | 75 +- sunspec2/tests/test_file_client.py | 70 +- sunspec2/tests/test_modbus_client.py | 146 ++- sunspec2/tests/test_smdx.py | 74 +- sunspec2/tests/test_spreadsheet.py | 171 +--- sunspec2/tests/test_xlsx.py | 1221 +++++++++++--------------- 6 files changed, 748 insertions(+), 1009 deletions(-) diff --git a/sunspec2/tests/test_device.py b/sunspec2/tests/test_device.py index a39f1a3..36ada75 100644 --- a/sunspec2/tests/test_device.py +++ b/sunspec2/tests/test_device.py @@ -1799,9 +1799,9 @@ def test_get_json(self): m.groups['Crv'][0].points['DeptRef'].sf_value = 3 m.groups['Crv'][0].points['Pri'].sf_required = True m.groups['Crv'][0].points['Pri'].sf_value = 3 - assert m.groups['Crv'][0].get_json(computed=True) == '''{"ActPt": 4, "DeptRef": 1000.0,''' + \ - ''' "Pri": 1000.0, "VRef": 1, "VRefAuto": 0, "VRefAutoEna": null, "VRefAutoTms": null,''' + \ - ''' "RspTms": 6, "ReadOnly": 1, "Pt": [{"V": 92.0, "Var": 30.0}, {"V": 96.7, "Var": 0.0},''' + \ + assert m.groups['Crv'][0].get_json(computed=True) == '''{"ActPt": 4, "DeptRef": 1000.0, "Pri": 1000.0,''' + \ + ''' "VRef": 0.01, "VRefAuto": 0.0, "VRefAutoEna": null, "VRefAutoTms": null, "RspTms": 6,''' + \ + ''' "ReadOnly": 1, "Pt": [{"V": 92.0, "Var": 30.0}, {"V": 96.7, "Var": 0.0},''' + \ ''' {"V": 103.0, "Var": 0.0}, {"V": 107.0, "Var": -30.0}]}''' def test_set_json(self): @@ -2042,9 +2042,9 @@ def test_get_mb(self): m.groups['Crv'][0].points['DeptRef'].sf_value = 3 m.groups['Crv'][0].points['Pri'].sf_required = True m.groups['Crv'][0].points['Pri'].sf_value = 3 - assert m.groups['Crv'][0].get_mb(computed=True) == b'\x00\x04\x03\xe8\x03\xe8\x00\x01\x00\x00\xff\xff\xff' \ - b'\xff\x00\x00\x00\x06\x00\x01\x00\\\x00\x1e\x00`\x00\x00' \ - b'\x00g\x00\x00\x00k\xff\xe2' + assert m.groups['Crv'][0].get_mb(computed=True) == b'\x00\x04\x03\xe8\x03\xe8\x00\x00\x00\x00\xff\xff\xff' \ + b'\xff\x00\x00\x00\x06\x00\x01\x00\\\x00\x1e\x00`\x00' \ + b'\x00\x00g\x00\x00\x00k\xff\xe2' def test_set_mb(self): gdata_705 = { @@ -2299,6 +2299,21 @@ def test__init__(self): assert m2.mid is None assert m2.device is None + def test_model_1(self): + + mdata = { + "ID": 1, + "L": 68, + "Mn": "Test manuf", + "Md": "Test model", + "Opt": "Test options", + "Vr": "Test version", + "SN": "Test serial num", + "DA": 12, + "Pad": 0 + } + m = device.Model(1, data=mdata) + def test__error(self): m = device.Model(704) m.add_error('test error') @@ -2467,16 +2482,16 @@ def test_get_dict(self): assert d.get_dict(computed=True) == {'name': None, 'did': None, 'models': [ {'ID': 705, 'L': 67, 'Ena': 1, 'AdptCrvReq': 0, 'AdptCrvRslt': 0, 'NPt': 4, 'NCrv': 3, 'RvrtTms': 0, 'RvrtRem': 0, 'RvrtCrv': 0, 'V_SF': -2, 'DeptRef_SF': -2, 'RspTms_SF': None, 'Crv': [ - {'ActPt': 4, 'DeptRef': 1000.0, 'Pri': 1000.0, 'VRef': 1, 'VRefAuto': 0, 'VRefAutoEna': None, + {'ActPt': 4, 'DeptRef': 1000.0, 'Pri': 1000.0, 'VRef': 0.01, 'VRefAuto': 0.0, 'VRefAutoEna': None, 'VRefAutoTms': None, 'RspTms': 6, 'ReadOnly': 1, 'Pt': [{'V': 92.0, 'Var': 30.0}, {'V': 96.7, 'Var': 0.0}, {'V': 103.0, 'Var': 0.0}, {'V': 107.0, 'Var': -30.0}]}, - {'ActPt': 4, 'DeptRef': 1, 'Pri': 1, 'VRef': 1, 'VRefAuto': 0, 'VRefAutoEna': None, 'VRefAutoTms': None, - 'RspTms': 6, 'ReadOnly': 0, + {'ActPt': 4, 'DeptRef': 1, 'Pri': 1, 'VRef': 0.01, 'VRefAuto': 0.0, 'VRefAutoEna': None, + 'VRefAutoTms': None, 'RspTms': 6, 'ReadOnly': 0, 'Pt': [{'V': 93.0, 'Var': 30.0}, {'V': 95.7, 'Var': 0.0}, {'V': 102.0, 'Var': 0.0}, {'V': 106.0, 'Var': -40.0}]}, - {'ActPt': 4, 'DeptRef': 1, 'Pri': 1, 'VRef': 1, 'VRefAuto': 0, 'VRefAutoEna': None, 'VRefAutoTms': None, - 'RspTms': 6, 'ReadOnly': 0, + {'ActPt': 4, 'DeptRef': 1, 'Pri': 1, 'VRef': 0.01, 'VRefAuto': 0.0, 'VRefAutoEna': None, + 'VRefAutoTms': None, 'RspTms': 6, 'ReadOnly': 0, 'Pt': [{'V': 94.0, 'Var': 20.0}, {'V': 95.7, 'Var': 0.0}, {'V': 105.0, 'Var': 0.0}, {'V': 108.0, 'Var': -20.0}]}], 'mid': None, 'error': '', 'model_id': 705}]} @@ -2603,17 +2618,18 @@ def test_get_json(self): m.groups['Crv'][0].points['DeptRef'].sf_value = 3 m.groups['Crv'][0].points['Pri'].sf_required = True m.groups['Crv'][0].points['Pri'].sf_value = 3 - assert d.get_json(computed=True) == '''{"name": null, "did": null, "models": [{"ID": 705,''' + \ - ''' "L": 67, "Ena": 1, "AdptCrvReq": 0, "AdptCrvRslt": 0, "NPt": 4, "NCrv": 3, "RvrtTms": 0,''' + \ - ''' "RvrtRem": 0, "RvrtCrv": 0, "V_SF": -2, "DeptRef_SF": -2, "RspTms_SF": null, ''' + \ - '''"Crv": [{"ActPt": 4, "DeptRef": 1000.0, "Pri": 1000.0, "VRef": 1, "VRefAuto": 0, ''' + \ - '''"VRefAutoEna": null, "VRefAutoTms": null, "RspTms": 6, "ReadOnly": 1, ''' + \ - '''"Pt": [{"V": 92.0, "Var": 30.0}, {"V": 96.7, "Var": 0.0}, {"V": 103.0, "Var": 0.0},''' + \ - ''' {"V": 107.0, "Var": -30.0}]}, {"ActPt": 4, "DeptRef": 1, "Pri": 1, "VRef": 1,''' + \ - ''' "VRefAuto": 0, "VRefAutoEna": null, "VRefAutoTms": null, "RspTms": 6, "ReadOnly": 0,''' + \ - ''' "Pt": [{"V": 93.0, "Var": 30.0}, {"V": 95.7, "Var": 0.0}, {"V": 102.0, "Var": 0.0},''' + \ - ''' {"V": 106.0, "Var": -40.0}]}, {"ActPt": 4, "DeptRef": 1, "Pri": 1, "VRef": 1, "VRefAuto": 0,''' + \ + assert d.get_json(computed=True) == '''{"name": null, "did": null, "models":''' + \ + ''' [{"ID": 705, "L": 67, "Ena": 1, "AdptCrvReq": 0, "AdptCrvRslt": 0,''' + \ + ''' "NPt": 4, "NCrv": 3, "RvrtTms": 0, "RvrtRem": 0, "RvrtCrv": 0, "V_SF": -2,''' + \ + ''' "DeptRef_SF": -2, "RspTms_SF": null, "Crv": [{"ActPt": 4, "DeptRef": 1000.0,''' + \ + ''' "Pri": 1000.0, "VRef": 0.01, "VRefAuto": 0.0, "VRefAutoEna": null,''' + \ + ''' "VRefAutoTms": null, "RspTms": 6, "ReadOnly": 1, "Pt": [{"V": 92.0, "Var": 30.0},''' + \ + ''' {"V": 96.7, "Var": 0.0}, {"V": 103.0, "Var": 0.0}, {"V": 107.0, "Var": -30.0}]},''' + \ + ''' {"ActPt": 4, "DeptRef": 1, "Pri": 1, "VRef": 0.01, "VRefAuto": 0.0,''' + \ ''' "VRefAutoEna": null, "VRefAutoTms": null, "RspTms": 6, "ReadOnly": 0,''' + \ + ''' "Pt": [{"V": 93.0, "Var": 30.0}, {"V": 95.7, "Var": 0.0}, {"V": 102.0, "Var": 0.0},''' + \ + ''' {"V": 106.0, "Var": -40.0}]}, {"ActPt": 4, "DeptRef": 1, "Pri": 1, "VRef": 0.01,''' + \ + ''' "VRefAuto": 0.0, "VRefAutoEna": null, "VRefAutoTms": null, "RspTms": 6, "ReadOnly": 0,''' + \ ''' "Pt": [{"V": 94.0, "Var": 20.0}, {"V": 95.7, "Var": 0.0}, {"V": 105.0, "Var": 0.0},''' + \ ''' {"V": 108.0, "Var": -20.0}]}], "mid": null, "error": "", "model_id": 705}]}''' @@ -2733,14 +2749,15 @@ def test_get_mb(self): m.groups['Crv'][0].points['DeptRef'].sf_value = 3 m.groups['Crv'][0].points['Pri'].sf_required = True m.groups['Crv'][0].points['Pri'].sf_value = 3 - assert d.get_mb(computed=True) == b'\x02\xc1\x00C\x00\x01\x00\x00\x00\x00\x00\x04\x00\x03\x00\x00\x00' \ - b'\x00\x00\x00\x00\x00\x00\x00\xff\xfe\xff\xfe\x80\x00\x00\x04\x03' \ - b'\xe8\x03\xe8\x00\x01\x00\x00\xff\xff\xff\xff\x00\x00\x00\x06\x00' \ - b'\x01\x00\\\x00\x1e\x00`\x00\x00\x00g\x00\x00\x00k\x00\x1e\x00\x04' \ - b'\x00\x01\x00\x01\x00\x01\x00\x00\xff\xff\xff\xff\x00\x00\x00\x06' \ - b'\x00\x00\x00]\x00\x1e\x00_\x00\x00\x00f\x00\x00\x00j\x00(\x00\x04' \ - b'\x00\x01\x00\x01\x00\x01\x00\x00\xff\xff\xff\xff\x00\x00\x00\x06' \ - b'\x00\x00\x00^\x00\x14\x00_\x00\x00\x00i\x00\x00\x00l\x00\x14' + assert d.get_mb(computed=True) == b'\x02\xc1\x00C\x00\x01\x00\x00\x00\x00\x00\x04\x00\x03\x00\x00\x00\x00\x00' \ + b'\x00\x00\x00\x00\x00\xff\xfe\xff\xfe\x80\x00\x00\x04\x03\xe8\x03\xe8\x00' \ + b'\x00\x00\x00\xff\xff\xff\xff\x00\x00\x00\x06\x00\x01\x00\\\x00\x1e\x00`' \ + b'\x00\x00\x00g\x00\x00\x00k\x00\x1e\x00\x04\x00\x01\x00\x01\x00\x00\x00' \ + b'\x00\xff\xff\xff\xff\x00\x00\x00\x06\x00\x00\x00]\x00\x1e\x00_\x00\x00' \ + b'\x00f\x00\x00\x00j\x00(\x00\x04\x00\x01\x00\x01\x00\x00\x00\x00\xff\xff' \ + b'\xff\xff\x00\x00\x00\x06\x00\x00\x00^\x00\x14\x00_\x00\x00\x00i\x00\x00' \ + b'\x00l\x00\x14' + def test_set_mb(self): d = device.Device() diff --git a/sunspec2/tests/test_file_client.py b/sunspec2/tests/test_file_client.py index 3ee9f61..9c35b54 100644 --- a/sunspec2/tests/test_file_client.py +++ b/sunspec2/tests/test_file_client.py @@ -1554,10 +1554,10 @@ def test_get_json(self): m.groups['Crv'][0].points['DeptRef'].sf_value = 3 m.groups['Crv'][0].points['Pri'].sf_required = True m.groups['Crv'][0].points['Pri'].sf_value = 3 - assert m.groups['Crv'][0].get_json(computed=True) == '''{"ActPt": 4, "DeptRef": 1000.0, "Pri": 1000.0,''' + \ - ''' "VRef": 1, "VRefAuto": 0, "VRefAutoEna": null, "VRefAutoTms": null, "RspTms": 6,''' + \ - ''' "ReadOnly": 1, "Pt": [{"V": 92.0, "Var": 30.0}, {"V": 96.7, "Var": 0.0},''' + \ - ''' {"V": 103.0, "Var": 0.0}, {"V": 107.0, "Var": -30.0}]}''' + assert m.groups['Crv'][0].get_json(computed=True) == '''{"ActPt": 4, "DeptRef": 1000.0,''' + \ + ''' "Pri": 1000.0, "VRef": 0.01, "VRefAuto": 0.0, "VRefAutoEna": null,''' + \ + ''' "VRefAutoTms": null, "RspTms": 6, "ReadOnly": 1, "Pt": [{"V": 92.0, "Var": 30.0},''' + \ + ''' {"V": 96.7, "Var": 0.0}, {"V": 103.0, "Var": 0.0}, {"V": 107.0, "Var": -30.0}]}''' def test_set_json(self): gdata_705 = { @@ -1797,9 +1797,9 @@ def test_get_mb(self): m.groups['Crv'][0].points['DeptRef'].sf_value = 3 m.groups['Crv'][0].points['Pri'].sf_required = True m.groups['Crv'][0].points['Pri'].sf_value = 3 - assert m.groups['Crv'][0].get_mb(computed=True) == b'\x00\x04\x03\xe8\x03\xe8\x00\x01\x00\x00\xff\xff' \ - b'\xff\xff\x00\x00\x00\x06\x00\x01\x00\\\x00\x1e\x00`' \ - b'\x00\x00\x00g\x00\x00\x00k\xff\xe2' + assert m.groups['Crv'][0].get_mb(computed=True) == b'\x00\x04\x03\xe8\x03\xe8\x00\x00\x00\x00\xff' \ + b'\xff\xff\xff\x00\x00\x00\x06\x00\x01\x00\\\x00' \ + b'\x1e\x00`\x00\x00\x00g\x00\x00\x00k\xff\xe2' def test_set_mb(self): gdata_705 = { @@ -2219,16 +2219,16 @@ def test_get_dict(self): assert d.get_dict(computed=True)['models'] == [ {'ID': 705, 'L': 67, 'Ena': 1, 'AdptCrvReq': 0, 'AdptCrvRslt': 0, 'NPt': 4, 'NCrv': 3, 'RvrtTms': 0, 'RvrtRem': 0, 'RvrtCrv': 0, 'V_SF': -2, 'DeptRef_SF': -2, 'RspTms_SF': None, 'Crv': [ - {'ActPt': 4, 'DeptRef': 1000.0, 'Pri': 1000.0, 'VRef': 1, 'VRefAuto': 0, 'VRefAutoEna': None, + {'ActPt': 4, 'DeptRef': 1000.0, 'Pri': 1000.0, 'VRef': 0.01, 'VRefAuto': 0.0, 'VRefAutoEna': None, 'VRefAutoTms': None, 'RspTms': 6, 'ReadOnly': 1, 'Pt': [{'V': 92.0, 'Var': 30.0}, {'V': 96.7, 'Var': 0.0}, {'V': 103.0, 'Var': 0.0}, {'V': 107.0, 'Var': -30.0}]}, - {'ActPt': 4, 'DeptRef': 1, 'Pri': 1, 'VRef': 1, 'VRefAuto': 0, 'VRefAutoEna': None, 'VRefAutoTms': None, - 'RspTms': 6, 'ReadOnly': 0, + {'ActPt': 4, 'DeptRef': 1, 'Pri': 1, 'VRef': 0.01, 'VRefAuto': 0.0, 'VRefAutoEna': None, + 'VRefAutoTms': None, 'RspTms': 6, 'ReadOnly': 0, 'Pt': [{'V': 93.0, 'Var': 30.0}, {'V': 95.7, 'Var': 0.0}, {'V': 102.0, 'Var': 0.0}, {'V': 106.0, 'Var': -40.0}]}, - {'ActPt': 4, 'DeptRef': 1, 'Pri': 1, 'VRef': 1, 'VRefAuto': 0, 'VRefAutoEna': None, 'VRefAutoTms': None, - 'RspTms': 6, 'ReadOnly': 0, + {'ActPt': 4, 'DeptRef': 1, 'Pri': 1, 'VRef': 0.01, 'VRefAuto': 0.0, 'VRefAutoEna': None, + 'VRefAutoTms': None, 'RspTms': 6, 'ReadOnly': 0, 'Pt': [{'V': 94.0, 'Var': 20.0}, {'V': 95.7, 'Var': 0.0}, {'V': 105.0, 'Var': 0.0}, {'V': 108.0, 'Var': -20.0}]}]}] @@ -2355,21 +2355,21 @@ def test_get_json(self): m.groups['Crv'][0].points['Pri'].sf_required = True m.groups['Crv'][0].points['Pri'].sf_value = 3 - get_json_output2 = '''{"name": null, "did": "''' + str(d.did) + '''", "models": [{"ID": 705,''' + \ - ''' "L": 67, "Ena": 1, "AdptCrvReq": 0, "AdptCrvRslt": 0, "NPt": 4, "NCrv": 3,''' + \ - ''' "RvrtTms": 0, "RvrtRem": 0, "RvrtCrv": 0, "V_SF": -2, "DeptRef_SF": -2,''' + \ - ''' "RspTms_SF": null, "Crv": [{"ActPt": 4, "DeptRef": 1000.0, "Pri": 1000.0,''' + \ - ''' "VRef": 1, "VRefAuto": 0, "VRefAutoEna": null, "VRefAutoTms": null, "RspTms": 6,''' + \ - ''' "ReadOnly": 1, "Pt": [{"V": 92.0, "Var": 30.0}, {"V": 96.7, "Var": 0.0},''' + \ - ''' {"V": 103.0, "Var": 0.0}, {"V": 107.0, "Var": -30.0}]}, {"ActPt": 4, "DeptRef": 1,''' + \ - ''' "Pri": 1, "VRef": 1, "VRefAuto": 0, "VRefAutoEna": null, "VRefAutoTms": null,''' + \ - ''' "RspTms": 6, "ReadOnly": 0, "Pt": [{"V": 93.0, "Var": 30.0}, {"V": 95.7,''' + \ - ''' "Var": 0.0}, {"V": 102.0, "Var": 0.0}, {"V": 106.0, "Var": -40.0}]},''' + \ - ''' {"ActPt": 4, "DeptRef": 1, "Pri": 1, "VRef": 1, "VRefAuto": 0,''' + \ - ''' "VRefAutoEna": null, "VRefAutoTms": null, "RspTms": 6, "ReadOnly": 0,''' + \ - ''' "Pt": [{"V": 94.0, "Var": 20.0}, {"V": 95.7, "Var": 0.0}, {"V": 105.0,''' + \ - ''' "Var": 0.0}, {"V": 108.0, "Var": -20.0}]}]}]}''' - + get_json_output2 = '''{"name": null, "did": "''' + str(d.did) + '''", "models": [{"ID": 705, "L": 67,''' + \ + ''' "Ena": 1, "AdptCrvReq": 0, "AdptCrvRslt": 0, "NPt": 4, "NCrv": 3,''' + \ + ''' "RvrtTms": 0, "RvrtRem": 0, "RvrtCrv": 0, "V_SF": -2,''' + \ + ''' "DeptRef_SF": -2, "RspTms_SF": null, "Crv": [{"ActPt": 4,''' + \ + ''' "DeptRef": 1000.0, "Pri": 1000.0, "VRef": 0.01, "VRefAuto": 0.0,''' + \ + ''' "VRefAutoEna": null, "VRefAutoTms": null, "RspTms": 6, "ReadOnly": 1,''' + \ + ''' "Pt": [{"V": 92.0, "Var": 30.0}, {"V": 96.7, "Var": 0.0}, {"V": 103.0,''' + \ + ''' "Var": 0.0}, {"V": 107.0, "Var": -30.0}]}, {"ActPt": 4, "DeptRef": 1,''' + \ + ''' "Pri": 1, "VRef": 0.01, "VRefAuto": 0.0, "VRefAutoEna": null,''' + \ + ''' "VRefAutoTms": null, "RspTms": 6, "ReadOnly": 0, "Pt": [{"V": 93.0,''' + \ + ''' "Var": 30.0}, {"V": 95.7, "Var": 0.0}, {"V": 102.0, "Var": 0.0}, {"V": 106.0,''' + \ + ''' "Var": -40.0}]}, {"ActPt": 4, "DeptRef": 1, "Pri": 1, "VRef": 0.01,''' + \ + ''' "VRefAuto": 0.0, "VRefAutoEna": null, "VRefAutoTms": null, "RspTms": 6,''' + \ + ''' "ReadOnly": 0, "Pt": [{"V": 94.0, "Var": 20.0}, {"V": 95.7, "Var": 0.0},''' + \ + ''' {"V": 105.0, "Var": 0.0}, {"V": 108.0, "Var": -20.0}]}]}]}''' assert d.get_json(computed=True) == get_json_output2 def test_get_mb(self): @@ -2489,14 +2489,14 @@ def test_get_mb(self): m.groups['Crv'][0].points['DeptRef'].sf_value = 3 m.groups['Crv'][0].points['Pri'].sf_required = True m.groups['Crv'][0].points['Pri'].sf_value = 3 - assert d.get_mb(computed=True) == b'\x02\xc1\x00C\x00\x01\x00\x00\x00\x00\x00\x04\x00\x03\x00\x00\x00' \ - b'\x00\x00\x00\x00\x00\x00\x00\xff\xfe\xff\xfe\x80\x00\x00\x04\x03' \ - b'\xe8\x03\xe8\x00\x01\x00\x00\xff\xff\xff\xff\x00\x00\x00\x06\x00' \ - b'\x01\x00\\\x00\x1e\x00`\x00\x00\x00g\x00\x00\x00k\xff\xe2\x00\x04' \ - b'\x00\x01\x00\x01\x00\x01\x00\x00\xff\xff\xff\xff\x00\x00\x00\x06' \ - b'\x00\x00\x00]\x00\x1e\x00_\x00\x00\x00f\x00\x00\x00j\xff\xd8\x00' \ - b'\x04\x00\x01\x00\x01\x00\x01\x00\x00\xff\xff\xff\xff\x00\x00\x00\x06' \ - b'\x00\x00\x00^\x00\x14\x00_\x00\x00\x00i\x00\x00\x00l\xff\xec' + assert d.get_mb(computed=True) == b'\x02\xc1\x00C\x00\x01\x00\x00\x00\x00\x00\x04\x00\x03\x00\x00\x00\x00' \ + b'\x00\x00\x00\x00\x00\x00\xff\xfe\xff\xfe\x80\x00\x00\x04\x03\xe8\x03' \ + b'\xe8\x00\x00\x00\x00\xff\xff\xff\xff\x00\x00\x00\x06\x00\x01\x00\\\x00' \ + b'\x1e\x00`\x00\x00\x00g\x00\x00\x00k\xff\xe2\x00\x04\x00\x01\x00\x01\x00' \ + b'\x00\x00\x00\xff\xff\xff\xff\x00\x00\x00\x06\x00\x00\x00]\x00\x1e\x00_' \ + b'\x00\x00\x00f\x00\x00\x00j\xff\xd8\x00\x04\x00\x01\x00\x01\x00\x00\x00' \ + b'\x00\xff\xff\xff\xff\x00\x00\x00\x06\x00\x00\x00^\x00\x14\x00_\x00\x00' \ + b'\x00i\x00\x00\x00l\xff\xec' def test_set_mb(self): d = file_client.FileClientDevice() diff --git a/sunspec2/tests/test_modbus_client.py b/sunspec2/tests/test_modbus_client.py index daa49ff..3cd5dc0 100644 --- a/sunspec2/tests/test_modbus_client.py +++ b/sunspec2/tests/test_modbus_client.py @@ -11,6 +11,9 @@ def test_read(self, monkeypatch): monkeypatch.setattr(socket, 'socket', MockSocket.mock_socket) monkeypatch.setattr(serial, 'Serial', MockPort.mock_port) + monkeypatch.setattr(client.SunSpecModbusClientDeviceTCP, 'connect', MockSocket.mock_tcp_connect) + monkeypatch.setattr(client.SunSpecModbusClientDeviceTCP, 'disconnect', MockSocket.mock_tcp_connect) + # tcp d_tcp = client.SunSpecModbusClientDeviceTCP(slave_id=1, ipaddr='127.0.0.1', ipport=8502) tcp_buffer = [b'\x00\x00\x00\x00\x00\t\x01\x03\x06', @@ -36,16 +39,16 @@ def test_read(self, monkeypatch): b'\x00\x00\x00\x00\x00\x00\x00\xff\xff\xff\xff\xff\xff\xff\xff', b'\x00\x00\x00\x00\x00\x05\x01\x03\x02', b'\xff\xff'] - d_tcp.client.connect() + d_tcp.connect() d_tcp.client.socket._set_buffer(tcp_buffer) d_tcp.scan() + assert d_tcp.common[0].SN.value == 'sn-123456789' assert not d_tcp.common[0].SN.dirty d_tcp.common[0].SN.value = 'will be overwritten by read' assert d_tcp.common[0].SN.value == 'will be overwritten by read' assert d_tcp.common[0].SN.dirty - d_tcp.client.socket.clear_buffer() tcp_p_buffer = [b'\x00\x00\x00\x00\x00#\x01\x03 ', b'sn-123456789\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'] @@ -102,6 +105,9 @@ def test_write(self, monkeypatch): monkeypatch.setattr(socket, 'socket', MockSocket.mock_socket) monkeypatch.setattr(serial, 'Serial', MockPort.mock_port) + monkeypatch.setattr(client.SunSpecModbusClientDeviceTCP, 'connect', MockSocket.mock_tcp_connect) + monkeypatch.setattr(client.SunSpecModbusClientDeviceTCP, 'disconnect', MockSocket.mock_tcp_connect) + # tcp d_tcp = client.SunSpecModbusClientDeviceTCP(slave_id=1, ipaddr='127.0.0.1', ipport=8502) tcp_buffer = [b'\x00\x00\x00\x00\x00\t\x01\x03\x06', @@ -159,7 +165,9 @@ def test_write(self, monkeypatch): # rtu d_rtu = client.SunSpecModbusClientDeviceRTU(slave_id=1, name="COM2") - rtu_buffer = [b'\x01\x03\x06Su', + rtu_buffer = [ + b'\x01\x83\x02\xc0\xf1', + b'\x01\x03\x06Su', b'nS\x00\x01\x8d\xe4', b'\x01\x03\x02\x00B', b'8u', @@ -217,6 +225,9 @@ def test_read(self, monkeypatch): monkeypatch.setattr(socket, 'socket', MockSocket.mock_socket) monkeypatch.setattr(serial, 'Serial', MockPort.mock_port) + monkeypatch.setattr(client.SunSpecModbusClientDeviceTCP, 'connect', MockSocket.mock_tcp_connect) + monkeypatch.setattr(client.SunSpecModbusClientDeviceTCP, 'disconnect', MockSocket.mock_tcp_connect) + # tcp d_tcp = client.SunSpecModbusClientDeviceTCP(slave_id=1, ipaddr='127.0.0.1', ipport=8502) tcp_buffer = [b'\x00\x00\x00\x00\x00\t\x01\x03\x06', @@ -330,6 +341,9 @@ def test_write(self, monkeypatch): monkeypatch.setattr(socket, 'socket', MockSocket.mock_socket) monkeypatch.setattr(serial, 'Serial', MockPort.mock_port) + monkeypatch.setattr(client.SunSpecModbusClientDeviceTCP, 'connect', MockSocket.mock_tcp_connect) + monkeypatch.setattr(client.SunSpecModbusClientDeviceTCP, 'disconnect', MockSocket.mock_tcp_connect) + # tcp d_tcp = client.SunSpecModbusClientDeviceTCP(slave_id=1, ipaddr='127.0.0.1', ipport=8502) tcp_buffer = [b'\x00\x00\x00\x00\x00\t\x01\x03\x06', @@ -402,7 +416,9 @@ def test_write(self, monkeypatch): # rtu d_rtu = client.SunSpecModbusClientDeviceRTU(slave_id=1, name="COM2") - rtu_buffer = [b'\x01\x03\x06Su', + rtu_buffer = [ + b'\x01\x83\x02\xc0\xf1', + b'\x01\x03\x06Su', b'nS\x00\x01\x8d\xe4', b'\x01\x03\x02\x00B', b'8u', @@ -473,22 +489,83 @@ def test_write_points(self): class TestSunSpecModbusClientModel: - def test___init__(self): - c = client.SunSpecModbusClientModel(704) - assert c.model_id == 704 - assert c.model_addr == 0 - assert c.model_len == 0 - assert c.model_def['id'] == 704 - assert c.error_info == '' - assert c.gdef['name'] == 'DERCtlAC' - assert c.mid is None - assert c.device is None - assert c.model == c - - def test_error(self): - c = client.SunSpecModbusClientModel(704) - c.add_error('test error') - assert c.error_info == 'test error\n' + def test___init__(self, monkeypatch): + d_rtu = client.SunSpecModbusClientDeviceRTU(slave_id=1, name="COM2") + monkeypatch.setattr(serial, 'Serial', MockPort.mock_port) + + rtu_buffer = [ + b'\x01\x83\x02\xc0\xf1', + b'\x01\x03\x06Su', + b'nS\x00\x01\x8d\xe4', + b'\x01\x03\x02\x00B', + b'8u', + b'\x01\x03\x88\x00\x01', + b'\x00BSunSpecTest\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00' + b'\x00\x00\x00TestDevice-1\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00' + b'\x00\x00\x00\x00opt_a_b_c\x00\x00\x00\x00\x00\x00\x001.2.3\x00\x00\x00\x00\x00\x00\x00\x00' + b'\x00\x00\x00sn-123456789\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00' + b'\x00\x00\x00\x00\x01\x00\x00M\xf9', + b'\x01\x03\x02\x00~', + b'8d', + b'\x01\x03\x02\x00@', + b'\xb9\xb4', + b'\x01\x03\x84\x00~', + b'\x00@\x00\x03\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\x80\x00\x80\x00\x80\x00' + b'\xff\xff\xff\xff\xff\xff\x80\x00\xff\xff\x80\x00\xff\xff\x80\x00\xff\xff\x80\x00\xff\xff' + b'\x80\x00\xff\xff\x80\x00\xff\xff\x80\x00\xff\xff\x80\x00\xff\xff\x80\x00\xff\xff\x80\x00' + b'\xff\xff\x80\x00\xff\xff\x80\x00\xff\xff\x80\x00\xff\xff\x80\x00\xff\xff\x80\x00\xff\xff' + b'\x80\x00\xff\xff\x80\x00\xff\xff\x80\x00\xff\xff\x80\x00\xff\xff\x80\x00\x00\x00\x00\x00' + b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xff\xff\xff\xff\xff\xff\xff\xff\xffI', + b'\x01\x03\x02\xff\xff', + b'\xb9\xf4'] + d_rtu.open() + d_rtu.client.serial._set_buffer(rtu_buffer) + d_rtu.scan() + client_model = d_rtu.models['common'][0] + assert client_model.model_id == 1 + assert client_model.model_addr == 40002 + assert client_model.model_len == 66 + assert client_model.model_def['id'] == 1 + assert client_model.error_info == '' + assert client_model.gdef['name'] == 'common' + assert client_model.mid is not None + assert client_model.__class__.__name__ == 'SunSpecModbusClientModel' + + def test_error(self, monkeypatch): + d_rtu = client.SunSpecModbusClientDeviceRTU(slave_id=1, name="COM2") + monkeypatch.setattr(serial, 'Serial', MockPort.mock_port) + + rtu_buffer = [ + b'\x01\x83\x02\xc0\xf1', + b'\x01\x03\x06Su', + b'nS\x00\x01\x8d\xe4', + b'\x01\x03\x02\x00B', + b'8u', + b'\x01\x03\x88\x00\x01', + b'\x00BSunSpecTest\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00' + b'\x00\x00\x00TestDevice-1\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00' + b'\x00\x00\x00\x00opt_a_b_c\x00\x00\x00\x00\x00\x00\x001.2.3\x00\x00\x00\x00\x00\x00\x00\x00' + b'\x00\x00\x00sn-123456789\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00' + b'\x00\x00\x00\x00\x01\x00\x00M\xf9', + b'\x01\x03\x02\x00~', + b'8d', + b'\x01\x03\x02\x00@', + b'\xb9\xb4', + b'\x01\x03\x84\x00~', + b'\x00@\x00\x03\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\x80\x00\x80\x00\x80\x00' + b'\xff\xff\xff\xff\xff\xff\x80\x00\xff\xff\x80\x00\xff\xff\x80\x00\xff\xff\x80\x00\xff\xff' + b'\x80\x00\xff\xff\x80\x00\xff\xff\x80\x00\xff\xff\x80\x00\xff\xff\x80\x00\xff\xff\x80\x00' + b'\xff\xff\x80\x00\xff\xff\x80\x00\xff\xff\x80\x00\xff\xff\x80\x00\xff\xff\x80\x00\xff\xff' + b'\x80\x00\xff\xff\x80\x00\xff\xff\x80\x00\xff\xff\x80\x00\xff\xff\x80\x00\x00\x00\x00\x00' + b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xff\xff\xff\xff\xff\xff\xff\xff\xffI', + b'\x01\x03\x02\xff\xff', + b'\xb9\xf4'] + d_rtu.open() + d_rtu.client.serial._set_buffer(rtu_buffer) + d_rtu.scan() + client_model = d_rtu.models['common'][0] + client_model.add_error('test error') + assert client_model.error_info == 'test error\n' class TestSunSpecModbusClientDevice: @@ -496,7 +573,7 @@ def test___init__(self): d = client.SunSpecModbusClientDevice() assert d.did assert d.retry_count == 2 - assert d.base_addr_list == [40000, 0, 50000] + assert d.base_addr_list == [0, 40000, 50000] assert d.base_addr is None def test_connect(self): @@ -517,14 +594,17 @@ def test_write(self): def test_scan(self, monkeypatch): # tcp scan monkeypatch.setattr(socket, 'socket', MockSocket.mock_socket) + monkeypatch.setattr(client.SunSpecModbusClientDeviceTCP, 'connect', MockSocket.mock_tcp_connect) + monkeypatch.setattr(client.SunSpecModbusClientDeviceTCP, 'disconnect', MockSocket.mock_tcp_connect) + c_tcp = client.SunSpecModbusClientDeviceTCP() - tcp_req_check = [b'\x00\x00\x00\x00\x00\x06\x01\x03\x9c@\x00\x03', - b'\x00\x00\x00\x00\x00\x06\x01\x03\x9cC\x00\x01', - b'\x00\x00\x00\x00\x00\x06\x01\x03\x9cB\x00D', - b'\x00\x00\x00\x00\x00\x06\x01\x03\x9c\x86\x00\x01', - b'\x00\x00\x00\x00\x00\x06\x01\x03\x9c\x87\x00\x01', - b'\x00\x00\x00\x00\x00\x06\x01\x03\x9c\x86\x00B', - b'\x00\x00\x00\x00\x00\x06\x01\x03\x9c\xc8\x00\x01'] + tcp_req_check = [b'\x00\x00\x00\x00\x00\x06\x01\x03\x00\x00\x00\x03', + b'\x00\x00\x00\x00\x00\x06\x01\x03\x00\x03\x00\x01', + b'\x00\x00\x00\x00\x00\x06\x01\x03\x00\x02\x00B', + b'\x00\x00\x00\x00\x00\x06\x01\x03\x00F\x00\x01', + b'\x00\x00\x00\x00\x00\x06\x01\x03\x00G\x00\x01', + b'\x00\x00\x00\x00\x00\x06\x01\x03\x00F\x00@', + b'\x00\x00\x00\x00\x00\x06\x01\x03\x00\x88\x00\x01'] tcp_buffer = [b'\x00\x00\x00\x00\x00\t\x01\x03\x06', b'SunS\x00\x01', b'\x00\x00\x00\x00\x00\x05\x01\x03\x02', @@ -560,13 +640,9 @@ def test_scan(self, monkeypatch): monkeypatch.setattr(serial, 'Serial', MockPort.mock_port) c_rtu = client.SunSpecModbusClientDeviceRTU(1, "COMM2") - rtu_req_check = [b'\x01\x03\x9c@\x00\x03*O', - b'\x01\x03\x9cC\x00\x01[\x8e', - b'\x01\x03\x9cB\x00D\xcb\xbd', - b'\x01\x03\x9c\x86\x00\x01K\xb3', - b'\x01\x03\x9c\x87\x00\x01\x1as', - b'\x01\x03\x9c\x86\x00B\nB', - b'\x01\x03\x9c\xc8\x00\x01+\xa4'] + rtu_req_check = [b'\x01\x03\x00\x00\x00\x03\x05\xcb', b'\x01\x03\x00\x03\x00\x01t\n', + b'\x01\x03\x00\x02\x00Bd;', b'\x01\x03\x00F\x00\x01e\xdf', b'\x01\x03\x00G\x00\x014\x1f', + b'\x01\x03\x00F\x00@\xa5\xef', b'\x01\x03\x00\x88\x00\x01\x04 '] rtu_buffer = [b'\x01\x03\x06Su', b'nS\x00\x01\x8d\xe4', b'\x01\x03\x02\x00B', diff --git a/sunspec2/tests/test_smdx.py b/sunspec2/tests/test_smdx.py index f923537..d74f9cb 100644 --- a/sunspec2/tests/test_smdx.py +++ b/sunspec2/tests/test_smdx.py @@ -17,68 +17,18 @@ def test_model_filename_to_id(): def test_from_smdx_file(): - smdx_304 = { - "id": 304, - "group": { - "name": "inclinometer", - "type": "group", - "points": [ - { - "name": "ID", - "value": 304, - "desc": "Model identifier", - "label": "Model ID", - "mandatory": "M", - "static": "S", - "type": "uint16" - }, - { - "name": "L", - "desc": "Model length", - "label": "Model Length", - "mandatory": "M", - "static": "S", - "type": "uint16" - } - ], - "groups": [ - { - "name": "incl", - "type": "group", - "count": 0, - "points": [ - { - "name": "Inclx", - "type": "int32", - "mandatory": "M", - "units": "Degrees", - "sf": -2, - "label": "X", - "desc": "X-Axis inclination" - }, - { - "name": "Incly", - "type": "int32", - "units": "Degrees", - "sf": -2, - "label": "Y", - "desc": "Y-Axis inclination" - }, - { - "name": "Inclz", - "type": "int32", - "units": "Degrees", - "sf": -2, - "label": "Z", - "desc": "Z-Axis inclination" - } - ] - } - ], - "label": "Inclinometer Model", - "desc": "Include to support orientation measurements" - } - } + smdx_304 = {'id': 304, 'group': {'name': 'inclinometer', 'type': 'group', 'points': [ + {'name': 'ID', 'value': 304, 'desc': 'Model identifier', 'label': 'Model ID', 'size': 1, 'mandatory': 'M', + 'static': 'S', 'type': 'uint16'}, + {'name': 'L', 'desc': 'Model length', 'label': 'Model Length', 'size': 1, 'mandatory': 'M', 'static': 'S', + 'type': 'uint16'}], 'groups': [{'name': 'incl', 'type': 'group', 'count': 0, 'points': [ + {'name': 'Inclx', 'type': 'int32', 'size': 2, 'mandatory': 'M', 'units': 'Degrees', 'sf': -2, 'label': 'X', + 'desc': 'X-Axis inclination'}, + {'name': 'Incly', 'type': 'int32', 'size': 2, 'units': 'Degrees', 'sf': -2, 'label': 'Y', + 'desc': 'Y-Axis inclination'}, + {'name': 'Inclz', 'type': 'int32', 'size': 2, 'units': 'Degrees', 'sf': -2, 'label': 'Z', + 'desc': 'Z-Axis inclination'}]}], 'label': 'Inclinometer Model', + 'desc': 'Include to support orientation measurements'}} assert smdx.from_smdx_file('sunspec2/models/smdx/smdx_00304.xml') == smdx_304 diff --git a/sunspec2/tests/test_spreadsheet.py b/sunspec2/tests/test_spreadsheet.py index 973711b..cc8f5b2 100644 --- a/sunspec2/tests/test_spreadsheet.py +++ b/sunspec2/tests/test_spreadsheet.py @@ -70,68 +70,18 @@ def test_from_spreadsheet(): ['', 2, 'Incly', '', '', 'int32', '', -2, 'Degrees', '', '', '', 'Y', 'Y-Axis inclination', ''], ['', 4, 'Inclz', '', '', 'int32', '', -2, 'Degrees', '', '', '', 'Z', 'Z-Axis inclination', ''] ] - model_def = { - "id": 304, - "group": { - "name": "inclinometer", - "type": "group", - "points": [ - { - "name": "ID", - "value": 304, - "desc": "Model identifier", - "label": "Model ID", - "mandatory": "M", - "static": "S", - "type": "uint16" - }, - { - "name": "L", - "desc": "Model length", - "label": "Model Length", - "mandatory": "M", - "static": "S", - "type": "uint16" - } - ], - "groups": [ - { - "name": "incl", - "type": "group", - "count": 0, - "points": [ - { - "name": "Inclx", - "type": "int32", - "mandatory": "M", - "units": "Degrees", - "sf": -2, - "label": "X", - "desc": "X-Axis inclination" - }, - { - "name": "Incly", - "type": "int32", - "units": "Degrees", - "sf": -2, - "label": "Y", - "desc": "Y-Axis inclination" - }, - { - "name": "Inclz", - "type": "int32", - "units": "Degrees", - "sf": -2, - "label": "Z", - "desc": "Z-Axis inclination" - } - ] - } - ], - "label": "Inclinometer Model", - "desc": "Include to support orientation measurements" - } - } + model_def = {'group': {'name': 'inclinometer', 'type': 'group', 'label': 'Inclinometer Model', + 'desc': 'Include to support orientation measurements', 'points': [ + {'name': 'ID', 'type': 'uint16', 'size': 1, 'mandatory': 'M', 'static': 'S', 'label': 'Model ID', + 'desc': 'Model identifier', 'value': 304}, + {'name': 'L', 'type': 'uint16', 'size': 1, 'mandatory': 'M', 'static': 'S', 'label': 'Model Length', + 'desc': 'Model length'}], 'groups': [{'name': 'incl', 'type': 'group', 'count': 0, 'points': [ + {'name': 'Inclx', 'type': 'int32', 'size': 2, 'sf': -2, 'units': 'Degrees', 'mandatory': 'M', 'label': 'X', + 'desc': 'X-Axis inclination'}, + {'name': 'Incly', 'type': 'int32', 'size': 2, 'sf': -2, 'units': 'Degrees', 'label': 'Y', + 'desc': 'Y-Axis inclination'}, + {'name': 'Inclz', 'type': 'int32', 'size': 2, 'sf': -2, 'units': 'Degrees', 'label': 'Z', + 'desc': 'Z-Axis inclination'}]}]}, 'id': 304} assert spreadsheet.from_spreadsheet(model_spreadsheet) == model_def @@ -140,14 +90,16 @@ def test_to_spreadsheet(): model_spreadsheet = [ ['Address Offset', 'Group Offset', 'Name', 'Value', 'Count', 'Type', 'Size', 'Scale Factor', 'Units', 'RW Access (RW)', 'Mandatory (M)', 'Static (S)', 'Label', 'Description'], - ['', '', 'inclinometer', '', '', 'group', '', '', '', '', '', '', 'Inclinometer Model', 'Include to support orientation measurements'], - [0, '', 'ID', 304, '', 'uint16', '', '', '', '', 'M', 'S', 'Model ID', 'Model identifier'], - [1, '', 'L', '', '', 'uint16', '', '', '', '', 'M', 'S', 'Model Length', 'Model length'], + ['', '', 'inclinometer', '', '', 'group', '', '', '', '', '', '', 'Inclinometer Model', + 'Include to support orientation measurements'], + [0, '', 'ID', 304, '', 'uint16', 1, '', '', '', 'M', 'S', 'Model ID', 'Model identifier'], + [1, '', 'L', '', '', 'uint16', 1, '', '', '', 'M', 'S', 'Model Length', 'Model length'], ['', '', 'inclinometer.incl', '', 0, 'group', '', '', '', '', '', '', '', ''], - ['', 0, 'Inclx', '', '', 'int32', '', -2, 'Degrees', '', 'M', '', 'X', 'X-Axis inclination'], - ['', 2, 'Incly', '', '', 'int32', '', -2, 'Degrees', '', '', '', 'Y', 'Y-Axis inclination'], - ['', 4, 'Inclz', '', '', 'int32', '', -2, 'Degrees', '', '', '', 'Z', 'Z-Axis inclination'] + ['', 0, 'Inclx', '', '', 'int32', 2, -2, 'Degrees', '', 'M', '', 'X', 'X-Axis inclination'], + ['', 2, 'Incly', '', '', 'int32', 2, -2, 'Degrees', '', '', '', 'Y', 'Y-Axis inclination'], + ['', 4, 'Inclz', '', '', 'int32', 2, -2, 'Degrees', '', '', '', 'Z', 'Z-Axis inclination'] ] + model_def = { "id": 304, "group": { @@ -271,16 +223,16 @@ def test_to_spreadsheet_group(): spreadsheet.to_spreadsheet_group(ss, model_def['group'], has_notes=False) assert ss == [ ['', '', 'DERCapacity', '', '', 'group', '', '', '', '', '', '', 'DER Capacity', 'DER capacity model.'], - ['', 0, 'ID', 702, '', 'uint16', '', '', '', '', 'M', 'S', 'DER Capacity Model ID', - 'DER capacity model id.'], - ['', 1, 'L', '', '', 'uint16', '', '', '', '', 'M', 'S', 'DER Capacity Model Length', + ['', 0, 'ID', 702, '', 'uint16', 1, '', '', '', 'M', 'S', 'DER Capacity Model ID', 'DER capacity model id.'], + ['', 1, 'L', '', '', 'uint16', 1, '', '', '', 'M', 'S', 'DER Capacity Model Length', 'DER capacity name model length.'], ['Nameplate Ratings - Specifies capacity ratings', '', '', '', '', '', '', '', '', '', '', '', '', ''], - ['', 2, 'WMaxRtg', '', '', 'uint16', '', 'W_SF', 'W', '', '', '', 'Active Power Max Rating', + ['', 2, 'WMaxRtg', '', '', 'uint16', 1, 'W_SF', 'W', '', '', '', 'Active Power Max Rating', 'Maximum active power rating at unity power factor in watts.'], ['', '', 'CAT_A', 1, '', '', '', '', '', '', '', '', '', ''], ['', '', 'CAT_B', 2, '', '', '', '', '', '', '', '', '', '']] + def test_to_spreadsheet_point(): point = { "access": "R", @@ -387,68 +339,19 @@ def test_spreadsheet_equal(): def test_from_csv(): - model_def = { - "id": 304, - "group": { - "name": "inclinometer", - "type": "group", - "points": [ - { - "name": "ID", - "value": 304, - "desc": "Model identifier", - "label": "Model ID", - "mandatory": "M", - "static": "S", - "type": "uint16" - }, - { - "name": "L", - "desc": "Model length", - "label": "Model Length", - "mandatory": "M", - "static": "S", - "type": "uint16" - } - ], - "groups": [ - { - "name": "incl", - "type": "group", - "count": 0, - "points": [ - { - "name": "Inclx", - "type": "int32", - "mandatory": "M", - "units": "Degrees", - "sf": -2, - "label": "X", - "desc": "X-Axis inclination" - }, - { - "name": "Incly", - "type": "int32", - "units": "Degrees", - "sf": -2, - "label": "Y", - "desc": "Y-Axis inclination" - }, - { - "name": "Inclz", - "type": "int32", - "units": "Degrees", - "sf": -2, - "label": "Z", - "desc": "Z-Axis inclination" - } - ] - } - ], - "label": "Inclinometer Model", - "desc": "Include to support orientation measurements" - } - } + model_def = {'group': {'name': 'inclinometer', 'type': 'group', 'label': 'Inclinometer Model', + 'desc': 'Include to support orientation measurements', 'points': [ + {'name': 'ID', 'type': 'uint16', 'size': 1, 'mandatory': 'M', 'static': 'S', 'label': 'Model ID', + 'desc': 'Model identifier', 'value': 304}, + {'name': 'L', 'type': 'uint16', 'size': 1, 'mandatory': 'M', 'static': 'S', 'label': 'Model Length', + 'desc': 'Model length'}], 'groups': [{'name': 'incl', 'type': 'group', 'count': 0, 'points': [ + {'name': 'Inclx', 'type': 'int32', 'size': 2, 'sf': -2, 'units': 'Degrees', 'mandatory': 'M', 'label': 'X', + 'desc': 'X-Axis inclination'}, + {'name': 'Incly', 'type': 'int32', 'size': 2, 'sf': -2, 'units': 'Degrees', 'label': 'Y', + 'desc': 'Y-Axis inclination'}, + {'name': 'Inclz', 'type': 'int32', 'size': 2, 'sf': -2, 'units': 'Degrees', 'label': 'Z', + 'desc': 'Z-Axis inclination'}]}]}, 'id': 304} + assert model_def == spreadsheet.from_csv('sunspec2/tests/test_data/smdx_304.csv') diff --git a/sunspec2/tests/test_xlsx.py b/sunspec2/tests/test_xlsx.py index 2c1c3bf..610bc96 100644 --- a/sunspec2/tests/test_xlsx.py +++ b/sunspec2/tests/test_xlsx.py @@ -57,718 +57,511 @@ def test_from_xlsx(): wb = xlsx.ModelWorkbook(filename='sunspec2/tests/test_data/wb_701-705.xlsx') with open('sunspec2/models/json/model_704.json') as f: model_json_704 = json.load(f) - from_xlsx_output = { - "group": { - "name": "DERCtlAC", - "type": "group", - "label": "DER AC Controls", - "desc": "DER AC controls model.", - "points": [ - { - "name": "ID", - "type": "uint16", - "mandatory": "M", - "static": "S", - "label": "Model ID", - "desc": "Model name model id.", - "value": 704 - }, - { - "name": "L", - "type": "uint16", - "mandatory": "M", - "static": "S", - "label": "Model Length", - "desc": "Model name model length." - }, - { - "name": "PFWInjEna", - "type": "enum16", - "access": "RW", - "label": "Power Factor Enable (W Inj) Enable", - "desc": "Power factor enable when injecting active power.", - "comments": [ - "Set Power Factor (when injecting active power)" - ], - "symbols": [ - { - "name": "DISABLED", - "value": 0, - "label": "Disabled", - "desc": "Function is disabled." - }, - { - "name": "ENABLED", - "value": 1, - "label": "Enabled", - "desc": "Function is enabled." - } - ] - }, - { - "name": "PFWInjEnaRvrt", - "type": "enum16", - "symbols": [ - { - "name": "DISABLED", - "value": 0, - "label": "Disabled", - "desc": "Function is disabled." - }, - { - "name": "ENABLED", - "value": 1, - "label": "Enabled", - "desc": "Function is enabled." - } - ] - }, - { - "name": "PFWInjRvrtTms", - "type": "uint32", - "units": "Secs", - "access": "RW", - "label": "PF Reversion Time (W Inj)", - "desc": "Power factor reversion timer when injecting active power." - }, - { - "name": "PFWInjRvrtRem", - "type": "uint32", - "units": "Secs", - "label": "PF Reversion Time Rem (W Inj)", - "desc": "Power factor reversion time remaining when injecting active power." - }, - { - "name": "PFWAbsEna", - "type": "enum16", - "access": "RW", - "label": "Power Factor Enable (W Abs) Enable", - "desc": "Power factor enable when absorbing active power.", - "comments": [ - "Set Power Factor (when absorbing active power)" - ], - "symbols": [ - { - "name": "DISABLED", - "value": 0, - "label": "Disabled", - "desc": "Function is disabled." - }, - { - "name": "ENABLED", - "value": 1, - "label": "Enabled", - "desc": "Function is enabled." - } - ] - }, - { - "name": "PFWAbsEnaRvrt", - "type": "enum16", - "symbols": [ - { - "name": "DISABLED", - "value": 0, - "label": "Disabled", - "desc": "Function is disabled." - }, - { - "name": "ENABLED", - "value": 1, - "label": "Enabled", - "desc": "Function is enabled." - } - ] - }, - { - "name": "PFWAbsRvrtTms", - "type": "uint32", - "units": "Secs", - "access": "RW", - "label": "PF Reversion Time (W Abs)", - "desc": "Power factor reversion timer when absorbing active power." - }, - { - "name": "PFWAbsRvrtRem", - "type": "uint32", - "units": "Secs", - "label": "PF Reversion Time Rem (W Abs)", - "desc": "Power factor reversion time remaining when absorbing active power." - }, - { - "name": "WMaxLimEna", - "type": "enum16", - "access": "RW", - "label": "Limit Max Active Power Enable", - "desc": "Limit maximum active power enable.", - "comments": [ - "Limit Maximum Active Power Generation" - ], - "symbols": [ - { - "name": "DISABLED", - "value": 0, - "label": "Disabled", - "desc": "Function is disabled." - }, - { - "name": "ENABLED", - "value": 1, - "label": "Enabled", - "desc": "Function is enabled." - } - ] - }, - { - "name": "WMaxLim", - "type": "uint16", - "sf": "WMaxLim_SF", - "units": "Pct", - "access": "RW", - "label": "Limit Max Power Setpoint", - "desc": "Limit maximum active power value." - }, - { - "name": "WMaxLimRvrt", - "type": "uint16", - "sf": "WMaxLim_SF", - "units": "Pct", - "access": "RW", - "label": "Reversion Limit Max Power", - "desc": "Reversion limit maximum active power value." - }, - { - "name": "WMaxLimEnaRvrt", - "type": "enum16", - "symbols": [ - { - "name": "DISABLED", - "value": 0, - "label": "Disabled", - "desc": "Function is disabled." - }, - { - "name": "ENABLED", - "value": 1, - "label": "Enabled", - "desc": "Function is enabled." - } - ] - }, - { - "name": "WMaxLimRvrtTms", - "type": "uint32", - "units": "Secs", - "access": "RW", - "label": "Limit Max Power Reversion Time", - "desc": "Limit maximum active power reversion time." - }, - { - "name": "WMaxLimRvrtRem", - "type": "uint32", - "units": "Secs", - "label": "Limit Max Power Rev Time Rem", - "desc": "Limit maximum active power reversion time remaining." - }, - { - "name": "WSetEna", - "type": "enum16", - "access": "RW", - "label": "Set Active Power Enable", - "desc": "Set active power enable.", - "comments": [ - "Set Active Power Level (may be negative for charging)" - ], - "symbols": [ - { - "name": "DISABLED", - "value": 0, - "label": "Disabled", - "desc": "Function is disabled." - }, - { - "name": "ENABLED", - "value": 1, - "label": "Enabled", - "desc": "Function is enabled." - } - ] - }, - { - "name": "WSetMod", - "type": "enum16", - "access": "RW", - "label": "Set Active Power Mode", - "desc": "Set active power mode.", - "symbols": [ - { - "name": "W_MAX_PCT", - "value": 1, - "label": "Active Power As Max Percent", - "desc": "Active power setting is percentage of maximum active power." - }, - { - "name": "WATTS", - "value": 2, - "label": "Active Power As Watts", - "desc": "Active power setting is in watts." - } - ] - }, - { - "name": "WSet", - "type": "int32", - "sf": "WSet_SF", - "units": "W", - "access": "RW", - "label": "Active Power Setpoint (W)", - "desc": "Active power setting value in watts." - }, - { - "name": "WSetRvrt", - "type": "int32", - "sf": "WSet_SF", - "units": "W", - "access": "RW", - "label": "Reversion Active Power (W)", - "desc": "Reversion active power setting value in watts." - }, - { - "name": "WSetPct", - "type": "int32", - "sf": "WSetPct_SF", - "units": "Pct", - "access": "RW", - "label": "Active Power Setpoint (Pct)", - "desc": "Active power setting value as percent." - }, - { - "name": "WSetPctRvrt", - "type": "int32", - "sf": "WSetPct_SF", - "units": "Pct", - "access": "RW", - "label": "Reversion Active Power (Pct)", - "desc": "Reversion active power setting value as percent." - }, - { - "name": "WSetEnaRvrt", - "type": "enum16", - "symbols": [ - { - "name": "DISABLED", - "value": 0, - "label": "Disabled", - "desc": "Function is disabled." - }, - { - "name": "ENABLED", - "value": 1, - "label": "Enabled", - "desc": "Function is enabled." - } - ] - }, - { - "name": "WSetRvrtTms", - "type": "uint32", - "units": "Secs", - "access": "RW", - "label": "Active Power Reversion Time", - "desc": "Set active power reversion time." - }, - { - "name": "WSetRvrtRem", - "type": "uint32", - "units": "Secs", - "label": "Active Power Rev Time Rem", - "desc": "Set active power reversion time remaining." - }, - { - "name": "VarSetEna", - "type": "enum16", - "access": "RW", - "label": "Set Reactive Power Enable", - "desc": "Set reactive power enable.", - "comments": [ - "Set Reacitve Power Level" - ], - "symbols": [ - { - "name": "DISABLED", - "value": 0, - "label": "Disabled", - "desc": "Function is disabled." - }, - { - "name": "ENABLED", - "value": 1, - "label": "Enabled", - "desc": "Function is enabled." - } - ] - }, - { - "name": "VarSetMod", - "type": "enum16", - "access": "RW", - "label": "Set Reactive Power Mode", - "desc": "Set reactive power mode.", - "symbols": [ - { - "name": "W_MAX_PCT", - "value": 1, - "label": "Reactive Power as Watt Max Pct", - "desc": "Reactive power setting is percent of maximum active power." - }, - { - "name": "VAR_MAX_PCT", - "value": 2, - "label": "Reactive Power as Var Max Pct", - "desc": "Reactive power setting is percent of maximum reactive power." - }, - { - "name": "VAR_AVAIL_PCT", - "value": 3, - "label": "Reactive Power as Var Avail Pct", - "desc": "Reactive power setting is percent of available reactive power." - }, - { - "name": "VARS", - "value": 4, - "label": "Reactive Power as Vars", - "desc": "Reactive power is in vars." - } - ] - }, - { - "name": "VarSetPri", - "type": "enum16", - "symbols": [ - { - "name": "ACTIVE", - "value": 1, - "label": "Active Power Priority", - "desc": "Active power priority." - }, - { - "name": "REACTIVE", - "value": 2, - "label": "Reactive Power Priority", - "desc": "Reactive power priority." - }, - { - "name": "IEEE_1547", - "value": 3, - "label": "IEEE 1547 Power Priority", - "desc": "IEEE 1547-2018 power priority mode." - }, - { - "name": "PF", - "value": 4, - "label": "PF Power Priority", - "desc": "Track PF setting derived from current active and reactive power settings." - }, - { - "name": "VENDOR", - "value": 5, - "label": "Vendor Power Priority", - "desc": "Power priority is vendor specific mode." - } - ] - }, - { - "name": "VarSet", - "type": "int32", - "sf": "VarSet_SF", - "units": "Var", - "access": "RW", - "label": "Reactive Power Setpoint (Vars)", - "desc": "Reactive power setting value in vars." - }, - { - "name": "VarSetRvrt", - "type": "int32", - "sf": "VarSet_SF", - "units": "Var", - "access": "RW", - "label": "Reversion Reactive Power (Vars)", - "desc": "Reversion reactive power setting value in vars." - }, - { - "name": "VarSetPct", - "type": "int32", - "sf": "VarSetPct_SF", - "units": "Pct", - "access": "RW", - "label": "Reactive Power Setpoint (Pct)", - "desc": "Reactive power setting value as percent." - }, - { - "name": "VarSetPctRvrt", - "type": "enum16", - "sf": "VarSetPct_SF", - "units": "Pct", - "access": "RW", - "label": "Reversion Reactive Power (Pct)", - "desc": "Reversion reactive power setting value as percent.", - "symbols": [ - { - "name": "DISABLED", - "value": 0, - "label": "Disabled", - "desc": "Function is disabled." - }, - { - "name": "ENABLED", - "value": 1, - "label": "Enabled", - "desc": "Function is enabled." - } - ] - }, - { - "name": "VarSetRvrtTms", - "type": "uint32", - "units": "Secs", - "access": "RW", - "label": "Reactive Power Reversion Time", - "desc": "Set reactive power reversion time." - }, - { - "name": "VarSetRvrtRem", - "type": "uint32", - "units": "Secs", - "label": "Reactive Power Rev Time Rem", - "desc": "Set reactive power reversion time remaining." - }, - { - "name": "RGra", - "type": "uint32", - "units": "%WMax/Sec", - "access": "RW", - "label": "Normal Ramp Rate", - "desc": "Ramp rate for increases in active power during normal generation.", - "comments": [ - "Ramp Rate" - ], - "symbols": [ - { - "name": "A_MAX", - "value": 1, - "label": "Max Current Ramp", - "desc": "Ramp based on percent of max current per second." - }, - { - "name": "W_MAX", - "value": 2, - "label": "Max Active Power Ramp", - "desc": "Ramp based on percent of max active power per second." - } - ] - }, - { - "name": "PF_SF", - "type": "sunssf", - "static": "S", - "label": "Power Factor Scale Factor", - "desc": "Power factor scale factor.", - "comments": [ - "Scale Factors" - ] - }, - { - "name": "WMaxLim_SF", - "type": "sunssf", - "static": "S", - "label": "Limit Max Power Scale Factor", - "desc": "Limit maximum power scale factor." - }, - { - "name": "WSet_SF", - "type": "sunssf", - "static": "S", - "label": "Active Power Scale Factor", - "desc": "Active power scale factor." - }, - { - "name": "WSetPct_SF", - "type": "sunssf", - "static": "S", - "label": "Active Power Pct Scale Factor", - "desc": "Active power pct scale factor." - }, - { - "name": "VarSet_SF", - "type": "sunssf", - "static": "S", - "label": "Reactive Power Scale Factor", - "desc": "Reactive power scale factor." - }, - { - "name": "VarSetPct_SF", - "type": "sunssf", - "static": "S", - "label": "Reactive Power Pct Scale Factor", - "desc": "Reactive power pct scale factor." - } - ], - "groups": [ - { - "name": "PFWInj", - "type": "sync", - "label": " ", - "desc": " ", - "comments": [ - "Power Factor Settings" - ], - "points": [ - { - "name": "PF", - "type": "uint16", - "sf": "PF_SF", - "access": "RW", - "label": "Power Factor (W Inj) ", - "desc": "Power factor setpoint when injecting active power." - }, - { - "name": "Ext", - "type": "enum16", - "access": "RW", - "label": "Power Factor Excitation (W Inj)", - "desc": "Power factor excitation setpoint when injecting active power.", - "symbols": [ - { - "name": "OVER_EXCITED", - "value": 0, - "label": "Over-excited", - "desc": "Power factor over-excited excitation." - }, - { - "name": "UNDER_EXCITED", - "value": 1, - "label": "Under-excited", - "desc": "Power factor under-excited excitation." - } - ] - } - ] - }, - { - "name": "PFWInjRvrt", - "type": "sync", - "label": " ", - "desc": " ", - "points": [ - { - "name": "PF", - "type": "uint16", - "sf": "PF_SF", - "access": "RW", - "label": "Reversion Power Factor (W Inj) ", - "desc": "Reversion power factor setpoint when injecting active power." - }, - { - "name": "Ext", - "type": "enum16", - "access": "RW", - "label": "Reversion PF Excitation (W Inj)", - "desc": "Reversion power factor excitation setpoint when injecting active power.", - "symbols": [ - { - "name": "OVER_EXCITED", - "value": 0, - "label": "Over-excited", - "desc": "Power factor over-excited excitation." - }, - { - "name": "UNDER_EXCITED", - "value": 1, - "label": "Under-excited", - "desc": "Power factor under-excited excitation." - } - ] - } - ] - }, - { - "name": "PFWAbs", - "type": "sync", - "label": " ", - "desc": " ", - "points": [ - { - "name": "PF", - "type": "uint16", - "sf": "PF_SF", - "access": "RW", - "label": "Power Factor (W Abs) ", - "desc": "Power factor setpoint when absorbing active power." - }, - { - "name": "Ext", - "type": "enum16", - "access": "RW", - "label": "Power Factor Excitation (W Abs)", - "desc": "Power factor excitation setpoint when absorbing active power.", - "symbols": [ - { - "name": "OVER_EXCITED", - "value": 0, - "label": "Over-excited", - "desc": "Power factor over-excited excitation." - }, - { - "name": "UNDER_EXCITED", - "value": 1, - "label": "Under-excited", - "desc": "Power factor under-excited excitation." - } - ] - } - ] - }, - { - "name": "PFWAbsRvrt", - "type": "sync", - "label": " ", - "desc": " ", - "points": [ - { - "name": "PF", - "type": "uint16", - "sf": "PF_SF", - "access": "RW", - "label": "Reversion Power Factor (W Abs) ", - "desc": "Reversion power factor setpoint when absorbing active power." - }, - { - "name": "Ext", - "type": "enum16", - "access": "RW", - "label": "Reversion PF Excitation (W Abs)", - "desc": "Reversion power factor excitation setpoint when absorbing active power.", - "symbols": [ - { - "name": "OVER_EXCITED", - "value": 0, - "label": "Over-excited", - "desc": "Power factor over-excited excitation." - }, - { - "name": "UNDER_EXCITED", - "value": 1, - "label": "Under-excited", - "desc": "Power factor under-excited excitation." - } - ] - } - ] - } - ] - }, - "id": 704 - } + from_xlsx_output = {'group': {'desc': 'DER AC controls model.', + 'groups': [{'comments': ['Power Factor Settings'], + 'desc': ' ', + 'label': ' ', + 'name': 'PFWInj', + 'points': [{'access': 'RW', + 'desc': 'Power factor setpoint when injecting active power.', + 'label': 'Power Factor (W Inj) ', + 'name': 'PF', + 'sf': 'PF_SF', + 'size': 1, + 'type': 'uint16'}, + {'access': 'RW', + 'desc': 'Power factor excitation setpoint when injecting active power.', + 'label': 'Power Factor Excitation (W Inj)', + 'name': 'Ext', + 'size': 1, + 'symbols': [{'desc': 'Power factor over-excited excitation.', + 'label': 'Over-excited', + 'name': 'OVER_EXCITED', + 'value': 0}, + {'desc': 'Power factor under-excited excitation.', + 'label': 'Under-excited', + 'name': 'UNDER_EXCITED', + 'value': 1}], + 'type': 'enum16'}], + 'type': 'sync'}, + {'desc': ' ', + 'label': ' ', + 'name': 'PFWInjRvrt', + 'points': [{'access': 'RW', + 'desc': 'Reversion power factor setpoint when injecting active power.', + 'label': 'Reversion Power Factor (W Inj) ', + 'name': 'PF', + 'sf': 'PF_SF', + 'size': 1, + 'type': 'uint16'}, + {'access': 'RW', + 'desc': 'Reversion power factor excitation setpoint when injecting active power.', + 'label': 'Reversion PF Excitation (W Inj)', + 'name': 'Ext', + 'size': 1, + 'symbols': [{'desc': 'Power factor over-excited excitation.', + 'label': 'Over-excited', + 'name': 'OVER_EXCITED', + 'value': 0}, + {'desc': 'Power factor under-excited excitation.', + 'label': 'Under-excited', + 'name': 'UNDER_EXCITED', + 'value': 1}], + 'type': 'enum16'}], + 'type': 'sync'}, + {'desc': ' ', + 'label': ' ', + 'name': 'PFWAbs', + 'points': [{'access': 'RW', + 'desc': 'Power factor setpoint when absorbing active power.', + 'label': 'Power Factor (W Abs) ', + 'name': 'PF', + 'sf': 'PF_SF', + 'size': 1, + 'type': 'uint16'}, + {'access': 'RW', + 'desc': 'Power factor excitation setpoint when absorbing active power.', + 'label': 'Power Factor Excitation (W Abs)', + 'name': 'Ext', + 'size': 1, + 'symbols': [{'desc': 'Power factor over-excited excitation.', + 'label': 'Over-excited', + 'name': 'OVER_EXCITED', + 'value': 0}, + {'desc': 'Power factor under-excited excitation.', + 'label': 'Under-excited', + 'name': 'UNDER_EXCITED', + 'value': 1}], + 'type': 'enum16'}], + 'type': 'sync'}, + {'desc': ' ', + 'label': ' ', + 'name': 'PFWAbsRvrt', + 'points': [{'access': 'RW', + 'desc': 'Reversion power factor setpoint when absorbing active power.', + 'label': 'Reversion Power Factor (W Abs) ', + 'name': 'PF', + 'sf': 'PF_SF', + 'size': 1, + 'type': 'uint16'}, + {'access': 'RW', + 'desc': 'Reversion power factor excitation setpoint when absorbing active power.', + 'label': 'Reversion PF Excitation (W Abs)', + 'name': 'Ext', + 'size': 1, + 'symbols': [{'desc': 'Power factor over-excited excitation.', + 'label': 'Over-excited', + 'name': 'OVER_EXCITED', + 'value': 0}, + {'desc': 'Power factor under-excited excitation.', + 'label': 'Under-excited', + 'name': 'UNDER_EXCITED', + 'value': 1}], + 'type': 'enum16'}], + 'type': 'sync'}], + 'label': 'DER AC Controls', + 'name': 'DERCtlAC', + 'points': [{'desc': 'Model name model id.', + 'label': 'Model ID', + 'mandatory': 'M', + 'name': 'ID', + 'size': 1, + 'static': 'S', + 'type': 'uint16', + 'value': 704}, + {'desc': 'Model name model length.', + 'label': 'Model Length', + 'mandatory': 'M', + 'name': 'L', + 'size': 1, + 'static': 'S', + 'type': 'uint16'}, + {'access': 'RW', + 'comments': ['Set Power Factor (when injecting active power)'], + 'desc': 'Power factor enable when injecting active power.', + 'label': 'Power Factor Enable (W Inj) Enable', + 'name': 'PFWInjEna', + 'size': 1, + 'symbols': [{'desc': 'Function is disabled.', + 'label': 'Disabled', + 'name': 'DISABLED', + 'value': 0}, + {'desc': 'Function is enabled.', + 'label': 'Enabled', + 'name': 'ENABLED', + 'value': 1}], + 'type': 'enum16'}, + {'name': 'PFWInjEnaRvrt', + 'size': 1, + 'symbols': [{'desc': 'Function is disabled.', + 'label': 'Disabled', + 'name': 'DISABLED', + 'value': 0}, + {'desc': 'Function is enabled.', + 'label': 'Enabled', + 'name': 'ENABLED', + 'value': 1}], + 'type': 'enum16'}, + {'access': 'RW', + 'desc': 'Power factor reversion timer when injecting active power.', + 'label': 'PF Reversion Time (W Inj)', + 'name': 'PFWInjRvrtTms', + 'size': 2, + 'type': 'uint32', + 'units': 'Secs'}, + {'desc': 'Power factor reversion time remaining when injecting active power.', + 'label': 'PF Reversion Time Rem (W Inj)', + 'name': 'PFWInjRvrtRem', + 'size': 2, + 'type': 'uint32', + 'units': 'Secs'}, + {'access': 'RW', + 'comments': ['Set Power Factor (when absorbing active power)'], + 'desc': 'Power factor enable when absorbing active power.', + 'label': 'Power Factor Enable (W Abs) Enable', + 'name': 'PFWAbsEna', + 'size': 1, + 'symbols': [{'desc': 'Function is disabled.', + 'label': 'Disabled', + 'name': 'DISABLED', + 'value': 0}, + {'desc': 'Function is enabled.', + 'label': 'Enabled', + 'name': 'ENABLED', + 'value': 1}], + 'type': 'enum16'}, + {'name': 'PFWAbsEnaRvrt', + 'size': 1, + 'symbols': [{'desc': 'Function is disabled.', + 'label': 'Disabled', + 'name': 'DISABLED', + 'value': 0}, + {'desc': 'Function is enabled.', + 'label': 'Enabled', + 'name': 'ENABLED', + 'value': 1}], + 'type': 'enum16'}, + {'access': 'RW', + 'desc': 'Power factor reversion timer when absorbing active power.', + 'label': 'PF Reversion Time (W Abs)', + 'name': 'PFWAbsRvrtTms', + 'size': 2, + 'type': 'uint32', + 'units': 'Secs'}, + {'desc': 'Power factor reversion time remaining when absorbing active power.', + 'label': 'PF Reversion Time Rem (W Abs)', + 'name': 'PFWAbsRvrtRem', + 'size': 2, + 'type': 'uint32', + 'units': 'Secs'}, + {'access': 'RW', + 'comments': ['Limit Maximum Active Power Generation'], + 'desc': 'Limit maximum active power enable.', + 'label': 'Limit Max Active Power Enable', + 'name': 'WMaxLimEna', + 'size': 1, + 'symbols': [{'desc': 'Function is disabled.', + 'label': 'Disabled', + 'name': 'DISABLED', + 'value': 0}, + {'desc': 'Function is enabled.', + 'label': 'Enabled', + 'name': 'ENABLED', + 'value': 1}], + 'type': 'enum16'}, + {'access': 'RW', + 'desc': 'Limit maximum active power value.', + 'label': 'Limit Max Power Setpoint', + 'name': 'WMaxLim', + 'sf': 'WMaxLim_SF', + 'size': 1, + 'type': 'uint16', + 'units': 'Pct'}, + {'access': 'RW', + 'desc': 'Reversion limit maximum active power value.', + 'label': 'Reversion Limit Max Power', + 'name': 'WMaxLimRvrt', + 'sf': 'WMaxLim_SF', + 'size': 1, + 'type': 'uint16', + 'units': 'Pct'}, + {'name': 'WMaxLimEnaRvrt', + 'size': 1, + 'symbols': [{'desc': 'Function is disabled.', + 'label': 'Disabled', + 'name': 'DISABLED', + 'value': 0}, + {'desc': 'Function is enabled.', + 'label': 'Enabled', + 'name': 'ENABLED', + 'value': 1}], + 'type': 'enum16'}, + {'access': 'RW', + 'desc': 'Limit maximum active power reversion time.', + 'label': 'Limit Max Power Reversion Time', + 'name': 'WMaxLimRvrtTms', + 'size': 2, + 'type': 'uint32', + 'units': 'Secs'}, + {'desc': 'Limit maximum active power reversion time remaining.', + 'label': 'Limit Max Power Rev Time Rem', + 'name': 'WMaxLimRvrtRem', + 'size': 2, + 'type': 'uint32', + 'units': 'Secs'}, + {'access': 'RW', + 'comments': ['Set Active Power Level (may be negative for charging)'], + 'desc': 'Set active power enable.', + 'label': 'Set Active Power Enable', + 'name': 'WSetEna', + 'size': 1, + 'symbols': [{'desc': 'Function is disabled.', + 'label': 'Disabled', + 'name': 'DISABLED', + 'value': 0}, + {'desc': 'Function is enabled.', + 'label': 'Enabled', + 'name': 'ENABLED', + 'value': 1}], + 'type': 'enum16'}, + {'access': 'RW', + 'desc': 'Set active power mode.', + 'label': 'Set Active Power Mode', + 'name': 'WSetMod', + 'size': 1, + 'symbols': [{'desc': 'Active power setting is percentage of maximum active power.', + 'label': 'Active Power As Max Percent', + 'name': 'W_MAX_PCT', + 'value': 1}, + {'desc': 'Active power setting is in watts.', + 'label': 'Active Power As Watts', + 'name': 'WATTS', + 'value': 2}], + 'type': 'enum16'}, + {'access': 'RW', + 'desc': 'Active power setting value in watts.', + 'label': 'Active Power Setpoint (W)', + 'name': 'WSet', + 'sf': 'WSet_SF', + 'size': 2, + 'type': 'int32', + 'units': 'W'}, + {'access': 'RW', + 'desc': 'Reversion active power setting value in watts.', + 'label': 'Reversion Active Power (W)', + 'name': 'WSetRvrt', + 'sf': 'WSet_SF', + 'size': 2, + 'type': 'int32', + 'units': 'W'}, + {'access': 'RW', + 'desc': 'Active power setting value as percent.', + 'label': 'Active Power Setpoint (Pct)', + 'name': 'WSetPct', + 'sf': 'WSetPct_SF', + 'size': 2, + 'type': 'int32', + 'units': 'Pct'}, + {'access': 'RW', + 'desc': 'Reversion active power setting value as percent.', + 'label': 'Reversion Active Power (Pct)', + 'name': 'WSetPctRvrt', + 'sf': 'WSetPct_SF', + 'size': 2, + 'type': 'int32', + 'units': 'Pct'}, + {'name': 'WSetEnaRvrt', + 'size': 1, + 'symbols': [{'desc': 'Function is disabled.', + 'label': 'Disabled', + 'name': 'DISABLED', + 'value': 0}, + {'desc': 'Function is enabled.', + 'label': 'Enabled', + 'name': 'ENABLED', + 'value': 1}], + 'type': 'enum16'}, + {'access': 'RW', + 'desc': 'Set active power reversion time.', + 'label': 'Active Power Reversion Time', + 'name': 'WSetRvrtTms', + 'size': 2, + 'type': 'uint32', + 'units': 'Secs'}, + {'desc': 'Set active power reversion time remaining.', + 'label': 'Active Power Rev Time Rem', + 'name': 'WSetRvrtRem', + 'size': 2, + 'type': 'uint32', + 'units': 'Secs'}, + {'access': 'RW', + 'comments': ['Set Reacitve Power Level'], + 'desc': 'Set reactive power enable.', + 'label': 'Set Reactive Power Enable', + 'name': 'VarSetEna', + 'size': 1, + 'symbols': [{'desc': 'Function is disabled.', + 'label': 'Disabled', + 'name': 'DISABLED', + 'value': 0}, + {'desc': 'Function is enabled.', + 'label': 'Enabled', + 'name': 'ENABLED', + 'value': 1}], + 'type': 'enum16'}, + {'access': 'RW', + 'desc': 'Set reactive power mode.', + 'label': 'Set Reactive Power Mode', + 'name': 'VarSetMod', + 'size': 1, + 'symbols': [{'desc': 'Reactive power setting is percent of maximum active power.', + 'label': 'Reactive Power as Watt Max Pct', + 'name': 'W_MAX_PCT', + 'value': 1}, + {'desc': 'Reactive power setting is percent of maximum reactive power.', + 'label': 'Reactive Power as Var Max Pct', + 'name': 'VAR_MAX_PCT', + 'value': 2}, + {'desc': 'Reactive power setting is percent of available reactive power.', + 'label': 'Reactive Power as Var Avail Pct', + 'name': 'VAR_AVAIL_PCT', + 'value': 3}, + {'desc': 'Reactive power is in vars.', + 'label': 'Reactive Power as Vars', + 'name': 'VARS', + 'value': 4}], + 'type': 'enum16'}, + {'name': 'VarSetPri', + 'size': 1, + 'symbols': [{'desc': 'Active power priority.', + 'label': 'Active Power Priority', + 'name': 'ACTIVE', + 'value': 1}, + {'desc': 'Reactive power priority.', + 'label': 'Reactive Power Priority', + 'name': 'REACTIVE', + 'value': 2}, + {'desc': 'IEEE 1547-2018 power priority mode.', + 'label': 'IEEE 1547 Power Priority', + 'name': 'IEEE_1547', + 'value': 3}, + {'desc': 'Track PF setting derived from current active and reactive power settings.', + 'label': 'PF Power Priority', + 'name': 'PF', + 'value': 4}, + {'desc': 'Power priority is vendor specific mode.', + 'label': 'Vendor Power Priority', + 'name': 'VENDOR', + 'value': 5}], + 'type': 'enum16'}, + {'access': 'RW', + 'desc': 'Reactive power setting value in vars.', + 'label': 'Reactive Power Setpoint (Vars)', + 'name': 'VarSet', + 'sf': 'VarSet_SF', + 'size': 2, + 'type': 'int32', + 'units': 'Var'}, + {'access': 'RW', + 'desc': 'Reversion reactive power setting value in vars.', + 'label': 'Reversion Reactive Power (Vars)', + 'name': 'VarSetRvrt', + 'sf': 'VarSet_SF', + 'size': 2, + 'type': 'int32', + 'units': 'Var'}, + {'access': 'RW', + 'desc': 'Reactive power setting value as percent.', + 'label': 'Reactive Power Setpoint (Pct)', + 'name': 'VarSetPct', + 'sf': 'VarSetPct_SF', + 'size': 2, + 'type': 'int32', + 'units': 'Pct'}, + {'access': 'RW', + 'desc': 'Reversion reactive power setting value as percent.', + 'label': 'Reversion Reactive Power (Pct)', + 'name': 'VarSetPctRvrt', + 'sf': 'VarSetPct_SF', + 'size': 1, + 'symbols': [{'desc': 'Function is disabled.', + 'label': 'Disabled', + 'name': 'DISABLED', + 'value': 0}, + {'desc': 'Function is enabled.', + 'label': 'Enabled', + 'name': 'ENABLED', + 'value': 1}], + 'type': 'enum16', + 'units': 'Pct'}, + {'access': 'RW', + 'desc': 'Set reactive power reversion time.', + 'label': 'Reactive Power Reversion Time', + 'name': 'VarSetRvrtTms', + 'size': 2, + 'type': 'uint32', + 'units': 'Secs'}, + {'desc': 'Set reactive power reversion time remaining.', + 'label': 'Reactive Power Rev Time Rem', + 'name': 'VarSetRvrtRem', + 'size': 2, + 'type': 'uint32', + 'units': 'Secs'}, + {'access': 'RW', + 'comments': ['Ramp Rate'], + 'desc': 'Ramp rate for increases in active power during normal generation.', + 'label': 'Normal Ramp Rate', + 'name': 'RGra', + 'size': 2, + 'symbols': [{'desc': 'Ramp based on percent of max current per second.', + 'label': 'Max Current Ramp', + 'name': 'A_MAX', + 'value': 1}, + {'desc': 'Ramp based on percent of max active power per second.', + 'label': 'Max Active Power Ramp', + 'name': 'W_MAX', + 'value': 2}], + 'type': 'uint32', + 'units': '%WMax/Sec'}, + {'comments': ['Scale Factors'], + 'desc': 'Power factor scale factor.', + 'label': 'Power Factor Scale Factor', + 'name': 'PF_SF', + 'size': 1, + 'static': 'S', + 'type': 'sunssf'}, + {'desc': 'Limit maximum power scale factor.', + 'label': 'Limit Max Power Scale Factor', + 'name': 'WMaxLim_SF', + 'size': 1, + 'static': 'S', + 'type': 'sunssf'}, + {'desc': 'Active power scale factor.', + 'label': 'Active Power Scale Factor', + 'name': 'WSet_SF', + 'size': 1, + 'static': 'S', + 'type': 'sunssf'}, + {'desc': 'Active power pct scale factor.', + 'label': 'Active Power Pct Scale Factor', + 'name': 'WSetPct_SF', + 'size': 1, + 'static': 'S', + 'type': 'sunssf'}, + {'desc': 'Reactive power scale factor.', + 'label': 'Reactive Power Scale Factor', + 'name': 'VarSet_SF', + 'size': 1, + 'static': 'S', + 'type': 'sunssf'}, + {'desc': 'Reactive power pct scale factor.', + 'label': 'Reactive Power Pct Scale Factor', + 'name': 'VarSetPct_SF', + 'size': 1, + 'static': 'S', + 'type': 'sunssf'}], + 'type': 'group'}, + 'id': 704} assert wb.from_xlsx(704) == from_xlsx_output @@ -829,8 +622,8 @@ def test_set_point(): wb.set_point(wb.wb['Index'], 2, values, 1) iter_rows = wb.xlsx_iter_rows(wb.wb['Index']) next(iter_rows) - assert next(iter_rows) == ['addr_offset', 'group_offset', 'name', 'value', 'count', - 'type', 'size', 'sf', 'units', 'access', 'mandatory', 'static', '', '', ''] + assert next(iter_rows) == ['addr_offset', 'group_offset', 'name', 'value', 'count', 'type', '', + 'sf', 'units', 'access', 'mandatory', 'static', '', '', ''] def test_set_symbol(): From 4a3d5c29b739005b0459061c0a99121cc1521d83 Mon Sep 17 00:00:00 2001 From: shelcrow Date: Fri, 30 Apr 2021 14:51:20 -0700 Subject: [PATCH 08/10] Added unknown point type exception to spreadsheet processing --- sunspec2/spreadsheet.py | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/sunspec2/spreadsheet.py b/sunspec2/spreadsheet.py index d2437de..df4d439 100644 --- a/sunspec2/spreadsheet.py +++ b/sunspec2/spreadsheet.py @@ -320,11 +320,17 @@ def to_spreadsheet_point(ss, point, has_notes, addr_offset=None, group_offset=No row[NAME_IDX] = name else: raise Exception('Point missing name attribute') + ptype = point.get(mdef.TYPE, '') - if ptype != '': - row[TYPE_IDX] = ptype - else: + + if ptype == '': raise Exception('Point %s missing type attribute' % name) + + if mdef.point_type_info.get(ptype) is None: + raise Exception('Unknown point type %s for point %s' % (ptype, name)) + + row[TYPE_IDX] = ptype + if addr_offset is not None: row[ADDRESS_OFFSET_IDX] = addr_offset elif group_offset is not None: From 235c9227380f7869bada8950bf1537ceaf5f9fc2 Mon Sep 17 00:00:00 2001 From: shelcrow Date: Fri, 30 Apr 2021 14:52:16 -0700 Subject: [PATCH 09/10] Refactored adding size attribute when loading in smdx files --- sunspec2/mdef.py | 11 +---------- sunspec2/smdx.py | 2 +- 2 files changed, 2 insertions(+), 11 deletions(-) diff --git a/sunspec2/mdef.py b/sunspec2/mdef.py index 926fc46..44e472b 100644 --- a/sunspec2/mdef.py +++ b/sunspec2/mdef.py @@ -114,14 +114,6 @@ MODEL_DEF_EXT = '.json' -def get_size(ptype): - tinfo = point_type_info.get(ptype) - if tinfo is not None: - return tinfo.get('len') - else: - raise ModelDefinitionError('Unknown point type %s.' % ptype) - - def to_int(x): try: return int(x, 0) @@ -459,5 +451,4 @@ def get_group_len_points_index(group_def): if __name__ == "__main__": - model_def = from_json_file('./models/json/model_711.json') - print(get_group_len_points_index(model_def.get(GROUP))) + pass diff --git a/sunspec2/smdx.py b/sunspec2/smdx.py index f942dad..8d32d9f 100644 --- a/sunspec2/smdx.py +++ b/sunspec2/smdx.py @@ -317,7 +317,7 @@ def from_smdx_point(element): raise mdef.ModelDefinitionError('Missing len attribute for point: %s' % pid) point_def[mdef.SIZE] = plen else: - point_def[mdef.SIZE] = mdef.get_size(ptype) + point_def[mdef.SIZE] = mdef.point_type_info.get(ptype)['len'] mandatory = element.attrib.get(SMDX_ATTR_MANDATORY, SMDX_MANDATORY_FALSE) if mandatory not in smdx_mandatory_types: raise mdef.ModelDefinitionError('Unknown mandatory type: %s' % mandatory) From 36197c81d68ea83453ef5a1b3bbbb73a20736561 Mon Sep 17 00:00:00 2001 From: shelcrow Date: Fri, 30 Apr 2021 14:52:48 -0700 Subject: [PATCH 10/10] Updated mock socket for testing --- sunspec2/tests/mock_socket.py | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/sunspec2/tests/mock_socket.py b/sunspec2/tests/mock_socket.py index d881601..3f7edb2 100644 --- a/sunspec2/tests/mock_socket.py +++ b/sunspec2/tests/mock_socket.py @@ -35,3 +35,15 @@ def clear_buffer(self): def mock_socket(AF_INET, SOCK_STREAM): return MockSocket() + + +def mock_tcp_connect(self): + if self.client.socket is None: + self.client.socket = mock_socket('foo', 'bar') + self.client.socket.settimeout(999) + self.client.socket.connect((999, 999)) + pass + + +def mock_tcp_disconnect(self): + pass \ No newline at end of file