From aa8a8b36baa52abdef7c22fea8fd64356aa650f5 Mon Sep 17 00:00:00 2001 From: AlexWells Date: Thu, 25 Aug 2022 13:45:09 +0100 Subject: [PATCH 1/2] Allow arrays of strings to be used with Waveforms Note the tests fail, largely due to now numpy defaults to a floating point dtype, which we cannot coerce into an array of strings. Also TODOs need completion... --- softioc/builder.py | 7 +++++++ softioc/device.py | 5 +++++ softioc/fields.py | 2 +- tests/test_record_values.py | 18 ++++++++++++++++++ 4 files changed, 31 insertions(+), 1 deletion(-) diff --git a/softioc/builder.py b/softioc/builder.py index 497e8b5e..0abee2e1 100644 --- a/softioc/builder.py +++ b/softioc/builder.py @@ -132,6 +132,8 @@ def Action(name, **fields): 'uint32': 'ULONG', 'float32': 'FLOAT', 'float64': 'DOUBLE', + 'bytes32': 'STRING', + 'bytes320': 'STRING', } # Coverts FTVL string to numpy type @@ -144,6 +146,7 @@ def Action(name, **fields): 'ULONG': 'uint32', 'FLOAT': 'float32', 'DOUBLE': 'float64', + 'STRING': 'S40', } @@ -195,11 +198,15 @@ def _waveform(value, fields): # Special case for [u]int64: if the initial value comes in as 64 bit # integers we cannot represent that, so recast it as [u]int32 + # Special case for array of strings to correctly identify each element + # of the array as a string type. if datatype is None: if initial_value.dtype == numpy.int64: initial_value = numpy.require(initial_value, numpy.int32) elif initial_value.dtype == numpy.uint64: initial_value = numpy.require(initial_value, numpy.uint32) + elif initial_value.dtype.char == "S": + initial_value = numpy.require(initial_value, numpy.dtype("S40")) else: initial_value = numpy.array([], dtype = datatype) length = _get_length(fields) diff --git a/softioc/device.py b/softioc/device.py index 0b5dad81..c0e82d78 100644 --- a/softioc/device.py +++ b/softioc/device.py @@ -391,6 +391,7 @@ def _read_value(self, record): return result def _write_value(self, record, value): + value = _require_waveform(value, self._dtype) nord = len(value) memmove( record.BPTR, value.ctypes.data_as(c_void_p), @@ -409,6 +410,10 @@ def _value_to_epics(self, value): # common class of bug, at the cost of duplicated code and data, here we # ensure a copy is taken of the value. assert len(value) <= self._nelm, 'Value too long for waveform' + # TODO: line below triggers "DeprecationWarning: Applying '+' to a + # non-numerical array is ill-defined. Returning a copy, but in the + # future this will error." + # Probably should change to numpy.copy? return +value def _epics_to_value(self, value): diff --git a/softioc/fields.py b/softioc/fields.py index 63fa476e..648c61a5 100644 --- a/softioc/fields.py +++ b/softioc/fields.py @@ -146,5 +146,5 @@ def __set_time(self, address, new_time): address[0].secs -= EPICS_epoch - +# TODO: DbfCodeToNumpy doesn't exist! __all__ = ['RecordFactory', 'DbfCodeToNumpy', 'ca_timestamp'] diff --git a/tests/test_record_values.py b/tests/test_record_values.py index 329298d8..6ac762f2 100644 --- a/tests/test_record_values.py +++ b/tests/test_record_values.py @@ -188,6 +188,24 @@ def record_values_names(fixture_value): ), numpy.ndarray, ), + ( + "wIn_byte_string_array", + builder.WaveformIn, + [b"AB", b"CD", b"EF"], + numpy.array( + [b"AB", b"CD", b"EF"], dtype=numpy.dtype("|S40") + ), + numpy.ndarray, + ), + ( + "wOut_byte_string_array", + builder.WaveformOut, + [b"AB", b"CD", b"EF"], + numpy.array( + [b"AB", b"CD", b"EF"], dtype=numpy.dtype("|S40") + ), + numpy.ndarray, + ), ( "longStringIn_str", builder.longStringIn, From b4385c6d6b2e66e1e2523d811a091b0889be1cc4 Mon Sep 17 00:00:00 2001 From: Tom Cobb Date: Thu, 15 Sep 2022 14:44:58 +0100 Subject: [PATCH 2/2] Add example enum Install p4p==p4p==4.1.2a1, epicscorelibs==7.0.7.99.0.0a1 to test --- example_pva.py | 38 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 38 insertions(+) create mode 100644 example_pva.py diff --git a/example_pva.py b/example_pva.py new file mode 100644 index 00000000..29def9c0 --- /dev/null +++ b/example_pva.py @@ -0,0 +1,38 @@ +from softioc import softioc, builder +from epicsdbbuilder.recordnames import RecordName + +builder.SetDeviceName("TC") + +record_name = "ENUM" +index = builder.longOut(record_name + ":INDEX", initial_value=1, on_update=print) +index.add_info( + "Q:group", + { + RecordName(record_name): { + "+id": "epics:nt/NTScalar:1.0", + "value.foo": {"+type": "structure", "+id": "enum_t"}, + "value.index": {"+type": "plain", "+channel": "VAL", "+putorder": 0}, + "display.description": {"+type": "plain", "+channel": "DESC"}, + "": {"+type": "meta", "+channel": "VAL"}, + } + }, +) + +choices = builder.WaveformOut( + record_name + ":CHOICES", + initial_value=["ZERO", "ONE", "MANY"], + FTVL="STRING", +) +choices.add_info( + "Q:group", + { + RecordName(record_name): { + "+id": "epics:nt/NTEnum:1.0", + "value.choices": {"+type": "plain", "+channel": "VAL"}, + } + }, +) + +builder.LoadDatabase() +softioc.iocInit() +softioc.interactive_ioc(globals())