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__