diff --git a/CHANGELOG.rst b/CHANGELOG.rst index 2f70f78b..40371658 100644 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -3,12 +3,18 @@ Changelog All notable changes to this project will be documented in this file. -The format is based on `Keep a Changelog `_, -and this project adheres to `Semantic Versioning `_. +The format is based on `Keep a Changelog +`_, and this project adheres to `Semantic +Versioning `_. -4.0.1_ - 2022-02-14 +Unreleased_ ----------- +- `Improve handling of range settings for scalar records <../../pull/82>`_ + +4.0.1_ - 2022-02-14 +------------------- + Removed: - `Remove python2 support <../../pull/64>`_ @@ -121,7 +127,9 @@ Added: Last release as an EPICS module rather than a Python package -.. _Unreleased: https://github.com/dls-controls/pythonIoc/compare/3.2...HEAD +.. _Unreleased: https://github.com/dls-controls/pythonIoc/compare/4.0.1...HEAD +.. _4.0.1: https://github.com/dls-controls/pythonIoc/compare/3.2.1...4.0.1 +.. _3.2.1: https://github.com/dls-controls/pythonIoc/compare/3.2...3.2.1 .. _3.2: https://github.com/dls-controls/pythonIoc/compare/3.1...3.2 .. _3.1: https://github.com/dls-controls/pythonIoc/compare/3.0...3.1 .. _3.0: https://github.com/dls-controls/pythonIoc/compare/3.0b2...3.0 diff --git a/docs/reference/api.rst b/docs/reference/api.rst index 20eb6de3..9afd2159 100644 --- a/docs/reference/api.rst +++ b/docs/reference/api.rst @@ -229,19 +229,20 @@ Test Facilities`_ documentation for more details of each function. For all of these functions any EPICS database field can be assigned a value by passing it as a keyword argument for the corresponding field name (in upper case) or by assigning to the corresponding field of the returned record object. -Thus the ``**fields`` argument in all of the definitions below refers to both the -optional keyword arguments listed above and record field names. +Thus the ``**fields`` argument in all of the definitions below refers to both +the optional keyword arguments listed above and record field names. All functions return a wrapped `ProcessDeviceSupportIn` or `ProcessDeviceSupportOut` instance. .. function:: - aIn(name, LOPR=None, HOPR=None, **fields) - aOut(name, LOPR=None, HOPR=None, **fields) + aIn(name, LOPR=None, HOPR=None, EGU=None, PREC=None, **fields) + aOut(name, DRVL=None, DRVH=None, EGU=None, PREC=None, **fields) - Create ``ai`` and ``ao`` records. The lower and upper limits for the - record can be specified, and if specified these will also be used to set the - ``EGUL`` and ``EGUF`` fields. + Create ``ai`` and ``ao`` records. The lower and upper limits for the record + can be specified. For ``ao`` records the ``LOPR`` and ``HOPR`` fields will + be set by default to the values of the ``DRVL`` and ``DRVH`` fields + respectively. .. function:: boolIn(name, ZNAM=None, ONAM=None, **fields) @@ -255,6 +256,10 @@ All functions return a wrapped `ProcessDeviceSupportIn` or longOut(name, DRVL=None, DRVH=None, EGU=None, **fields) Create ``longin`` and ``longout`` records with specified limits and units. + The lower and upper display limits for the record can be specified. For + ``longout`` records the ``LOPR`` and ``HOPR`` fields will be set by default + to the values of the ``DRVL`` and ``DRVH`` fields respectively. + .. function:: stringIn(name, **fields) diff --git a/softioc/builder.py b/softioc/builder.py index 23b435cf..591141c5 100644 --- a/softioc/builder.py +++ b/softioc/builder.py @@ -18,45 +18,52 @@ # The SCAN field for all input records defaults to I/O Intr. -def _in_record(record, name, **fields): - '''For input records we provide some automatic extra features: scanning, - initialisation as appropriate, and blocking puts from outside the IOC.''' - +def _set_in_defaults(fields): fields.setdefault('SCAN', 'I/O Intr') - if 'initial_value' in fields: - fields.setdefault('PINI', 'YES') + fields.setdefault('PINI', 'YES') fields.setdefault('DISP', 1) - return getattr(PythonDevice, record)(name, **fields) + +def _set_out_defaults(fields): + fields.setdefault('OMSL', 'supervisory') + +# For longout and ao we want DRV{L,H} to match {L,H}OPR by default +def _set_scalar_out_defaults(fields, DRVL, DRVH): + fields['DRVL'] = DRVL + fields['DRVH'] = DRVH + fields.setdefault('LOPR', DRVL) + fields.setdefault('HOPR', DRVH) -def aIn(name, LOPR=None, HOPR=None, **fields): - return _in_record( - 'ai', name, LOPR = LOPR, HOPR = HOPR, **fields) +def aIn(name, LOPR=None, HOPR=None, EGU=None, PREC=None, **fields): + _set_in_defaults(fields) + return PythonDevice.ai( + name, LOPR = LOPR, HOPR = HOPR, EGU = EGU, PREC = PREC, **fields) -def aOut(name, LOPR=None, HOPR=None, **fields): - fields.setdefault('DRVL', LOPR) - fields.setdefault('DRVH', HOPR) - return PythonDevice.ao( - name, LOPR = LOPR, HOPR = HOPR, **fields) +def aOut(name, DRVL=None, DRVH=None, EGU=None, PREC=None, **fields): + _set_out_defaults(fields) + _set_scalar_out_defaults(fields, DRVL, DRVH) + return PythonDevice.ao(name, EGU = EGU, PREC = PREC, **fields) def boolIn(name, ZNAM=None, ONAM=None, **fields): - return _in_record('bi', name, ZNAM = ZNAM, ONAM = ONAM, **fields) + _set_in_defaults(fields) + return PythonDevice.bi(name, ZNAM = ZNAM, ONAM = ONAM, **fields) def boolOut(name, ZNAM=None, ONAM=None, **fields): - return PythonDevice.bo( - name, OMSL = 'supervisory', ZNAM = ZNAM, ONAM = ONAM, **fields) + _set_out_defaults(fields) + return PythonDevice.bo(name, ZNAM = ZNAM, ONAM = ONAM, **fields) def longIn(name, LOPR=None, HOPR=None, EGU=None, **fields): + _set_in_defaults(fields) fields.setdefault('MDEL', -1) - return _in_record( - 'longin', name, EGU = EGU, LOPR = LOPR, HOPR = HOPR, **fields) + return PythonDevice.longin( + name, LOPR = LOPR, HOPR = HOPR, EGU = EGU, **fields) def longOut(name, DRVL=None, DRVH=None, EGU=None, **fields): - return PythonDevice.longout( - name, OMSL = 'supervisory', DRVL = DRVL, DRVH = DRVH, EGU = EGU, - **fields) + _set_out_defaults(fields) + _set_scalar_out_defaults(fields, DRVL, DRVH) + return PythonDevice.longout(name, EGU = EGU, **fields) # Field name prefixes for mbbi/mbbo records. @@ -92,17 +99,21 @@ def process_value(prefix, value, option, severity=None): def mbbIn(name, *options, **fields): _process_mbb_values(options, fields) - return _in_record('mbbi', name, **fields) + _set_in_defaults(fields) + return PythonDevice.mbbi(name, **fields) def mbbOut(name, *options, **fields): _process_mbb_values(options, fields) - return PythonDevice.mbbo(name, OMSL = 'supervisory', **fields) + _set_out_defaults(fields) + return PythonDevice.mbbo(name, **fields) def stringIn(name, **fields): - return _in_record('stringin', name, **fields) + _set_in_defaults(fields) + return PythonDevice.stringin(name, **fields) def stringOut(name, **fields): + _set_out_defaults(fields) return PythonDevice.stringout(name, **fields) def Action(name, **fields): @@ -204,7 +215,8 @@ def _waveform(value, fields): def Waveform(name, *value, **fields): _waveform(value, fields) - return _in_record('waveform', name, **fields) + _set_in_defaults(fields) + return PythonDevice.waveform(name, **fields) WaveformIn = Waveform @@ -232,7 +244,8 @@ def _long_string(fields): def longStringIn(name, **fields): _long_string(fields) - return _in_record('long_stringin', name, **fields) + _set_in_defaults(fields) + return PythonDevice.long_stringin(name, **fields) def longStringOut(name, **fields): _long_string(fields) diff --git a/tests/expected_records.db b/tests/expected_records.db index ab764e2f..f9faaccb 100644 --- a/tests/expected_records.db +++ b/tests/expected_records.db @@ -10,6 +10,7 @@ record(ai, "TS-DI-TEST-01:AI") record(ao, "TS-DI-TEST-01:AO") { field(DTYP, "Python") + field(OMSL, "supervisory") field(OUT, "@TS-DI-TEST-01:AO") } @@ -107,6 +108,8 @@ record(longout, "TS-DI-TEST-01:SINN") field(DRVH, "1024") field(DRVL, "0") field(DTYP, "Python") + field(HOPR, "1024") + field(LOPR, "0") field(OMSL, "supervisory") field(OUT, "@TS-DI-TEST-01:SINN") } @@ -114,6 +117,7 @@ record(longout, "TS-DI-TEST-01:SINN") record(ao, "TS-DI-TEST-01:SINP") { field(DTYP, "Python") + field(OMSL, "supervisory") field(OUT, "@TS-DI-TEST-01:SINP") } @@ -129,6 +133,7 @@ record(stringin, "TS-DI-TEST-01:STRINGIN") record(stringout, "TS-DI-TEST-01:STRINGOUT") { field(DTYP, "Python") + field(OMSL, "supervisory") field(OUT, "@TS-DI-TEST-01:STRINGOUT") } diff --git a/tests/test_record_values.py b/tests/test_record_values.py index 20339121..15f77b90 100644 --- a/tests/test_record_values.py +++ b/tests/test_record_values.py @@ -591,16 +591,8 @@ def test_value_post_init_set(self): """Test that records provide the expected values on get calls when using .set() before IOC initialisation and caget after initialisation""" - # Various conditions mean we cannot use the entire list of cases - filtered_list = [] - for item in record_values_list: - # .set() on In records doesn't update correctly. - # pythonSoftIOC issue #67 - if item[1] not in in_records: - filtered_list.append(item) - run_test_function( - filtered_list, SetValueEnum.SET_BEFORE_INIT, GetValueEnum.CAGET + record_values_list, SetValueEnum.SET_BEFORE_INIT, GetValueEnum.CAGET ) @requires_cothread diff --git a/tests/test_records.py b/tests/test_records.py index 9841e8cf..210f6327 100644 --- a/tests/test_records.py +++ b/tests/test_records.py @@ -131,6 +131,55 @@ def test_waveform_construction(): with pytest.raises(AssertionError): builder.WaveformIn("WI11", length=11, NELM=12) +def test_drvhl_hlopr_defaults(): + """Test the DRVH/L and H/LOPR default settings""" + # DRVH/L doesn't exist on In records + ai = builder.aIn("FOO") + # KeyError as fields with no value are simply not present in + # epicsdbbuilder's dictionary of fields + with pytest.raises(KeyError): + assert ai.LOPR.Value() is None + with pytest.raises(KeyError): + assert ai.HOPR.Value() is None + + ao = builder.aOut("BAR") + with pytest.raises(KeyError): + assert ao.LOPR.Value() is None + with pytest.raises(KeyError): + assert ao.HOPR.Value() is None + with pytest.raises(KeyError): + assert ao.DRVH.Value() is None + with pytest.raises(KeyError): + assert ao.DRVL.Value() is None + +def test_hlopr_inherits_drvhl(): + """Test that H/LOPR values are set to the DRVH/L values""" + lo = builder.longOut("ABC", DRVH=5, DRVL=10) + + assert lo.DRVH.Value() == 5 + assert lo.HOPR.Value() == 5 + assert lo.DRVL.Value() == 10 + assert lo.LOPR.Value() == 10 + +def test_hlopr_dvrhl_different_values(): + """Test you can set H/LOPR and DRVH/L to different values""" + ao = builder.aOut("DEF", DRVL=1, LOPR=2, HOPR=3, DRVH=4) + + assert ao.DRVL.Value() == 1 + assert ao.LOPR.Value() == 2 + assert ao.HOPR.Value() == 3 + assert ao.DRVH.Value() == 4 + +def test_pini_always_on(): + """Test that PINI is always on for in records regardless of initial_value""" + bi = builder.boolIn("AAA") + assert bi.PINI.Value() == "YES" + + mbbi = builder.mbbIn("BBB", initial_value=5) + assert mbbi.PINI.Value() == "YES" + + + def validate_fixture_names(params): """Provide nice names for the out_records fixture in TestValidate class""" return params[0].__name__