Skip to content

Commit bdab3d8

Browse files
committed
Revisit waveform initialisation
Ensure that the waveform type and length are specified during record creation to avoid problems with .set() later on.
1 parent 63d26d9 commit bdab3d8

File tree

4 files changed

+59
-56
lines changed

4 files changed

+59
-56
lines changed

softioc/builder.py

Lines changed: 41 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,8 @@
77
InitialiseDbd()
88
LoadDbdFile(os.path.join(os.path.dirname(__file__), 'device.dbd'))
99

10-
from . import pythonSoftIoc # noqa
10+
from . import device, pythonSoftIoc # noqa
11+
1112
PythonDevice = pythonSoftIoc.PythonDevice()
1213

1314

@@ -120,7 +121,7 @@ def Action(name, **fields):
120121
'l': 'LONG', # int_
121122
'f': 'FLOAT', # single
122123
'd': 'DOUBLE', # float_
123-
'S': 'STRING', # str_
124+
# 'S': 'STRING', # str_
124125

125126
# The following type codes are weakly supported by pretending that
126127
# they're related types.
@@ -135,6 +136,19 @@ def Action(name, **fields):
135136
# O object_ U unicode_ V void
136137
}
137138

139+
# Coverts FTVL string to numpy type
140+
DbfStringToNumpy = {
141+
# 'STRING': numpy.dtype('S40'), # Don't think we want this!
142+
'CHAR': numpy.dtype('int8'),
143+
'UCHAR': numpy.dtype('uint8'),
144+
'SHORT': numpy.dtype('int16'),
145+
'USHORT': numpy.dtype('uint16'),
146+
'LONG': numpy.dtype('int32'),
147+
'ULONG': numpy.dtype('uint32'),
148+
'FLOAT': numpy.dtype('float32'),
149+
'DOUBLE': numpy.dtype('float64'),
150+
}
151+
138152

139153
def _waveform(value, fields):
140154
'''Helper routine for waveform construction. If a value is given it is
@@ -146,29 +160,34 @@ def _waveform(value, fields):
146160
assert not value, 'Can\'t specify initial value twice!'
147161
value = (fields.pop('initial_value'),)
148162

163+
# Datatype can be specified as keyword argument, taken from FTVL, or derived
164+
# from the initial value
165+
if 'datatype' in fields:
166+
assert 'FTVL' not in fields, \
167+
'Can\'t specify FTVL and datatype together'
168+
datatype = numpy.dtype(fields.pop('datatype'))
169+
elif 'FTVL' in fields:
170+
datatype = DbfStringToNumpy[fields['FTVL']]
171+
else:
172+
# No datatype specified, will have to infer from initial value
173+
datatype = None
174+
149175
if value:
150-
# If a value is specified it should be the *only* non keyword
151-
# argument.
176+
# If a value is specified it should be the *only* non keyword argument.
152177
value, = value
153-
value = numpy.array(value)
154-
fields['initial_value'] = value
155-
156-
# Pick up default length and datatype from initial value
157-
length = len(value)
158-
FTVL = NumpyCharCodeToFtvl[value.dtype.char]
178+
initial_value = device._require_waveform(value, datatype)
179+
length = fields.pop('length', len(initial_value))
159180
else:
160-
# No value specified, so require length and datatype to be specified.
181+
initial_value = numpy.array([], dtype = datatype)
161182
length = fields.pop('length')
162-
FTVL = 'FLOAT'
183+
datatype = initial_value.dtype
163184

164-
datatype = fields.pop('datatype', None)
165-
if datatype is not None:
166-
assert 'FTVL' not in fields, \
167-
'Can\'t specify FTVL and datatype together'
168-
FTVL = NumpyCharCodeToFtvl[numpy.dtype(datatype).char]
185+
fields['initial_value'] = initial_value
186+
fields['_wf_nelm'] = length
187+
fields['_wf_dtype'] = datatype
169188

170189
fields['NELM'] = length
171-
fields.setdefault('FTVL', FTVL)
190+
fields['FTVL'] = NumpyCharCodeToFtvl[datatype.char]
172191

173192

174193
def Waveform(name, *value, **fields):
@@ -191,6 +210,10 @@ def _long_string(fields):
191210
# Default length of 256
192211
length = 256
193212

213+
fields.setdefault('initial_value', '')
214+
fields['_wf_nelm'] = length
215+
fields['_wf_dtype'] = numpy.dtype('uint8')
216+
194217
fields['NELM'] = length
195218
fields['FTVL'] = 'UCHAR'
196219

softioc/device.py

Lines changed: 16 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -314,6 +314,14 @@ class ao(ProcessDeviceSupportOut):
314314
_dbf_type_ = fields.DBF_DOUBLE
315315

316316

317+
def _require_waveform(value, dtype):
318+
value = numpy.require(value, dtype = dtype)
319+
if value.shape == ():
320+
value.shape = (1,)
321+
assert value.ndim == 1, 'Can\'t write multidimensional arrays'
322+
return value
323+
324+
317325
class WaveformBase(ProcessDeviceSupportCore):
318326
_link_ = 'INP'
319327
# In the waveform record class, the following four fields are key:
@@ -322,16 +330,15 @@ class WaveformBase(ProcessDeviceSupportCore):
322330
# NELM Length of allocated array in number of elements
323331
# NORD Currently reported length of array (0 <= NORD <= NELM)
324332
_fields_ = ['UDF', 'FTVL', 'BPTR', 'NELM', 'NORD']
325-
# Allow set() to be called before init_record:
326-
_dtype = None
327333

328-
def _default_value(self):
329-
return numpy.array([])
334+
335+
def __init__(self, name, _wf_nelm, _wf_dtype, **kargs):
336+
self._dtype = _wf_dtype
337+
self._nelm = _wf_nelm
338+
self.__super.__init__(name, **kargs)
330339

331340
def init_record(self, record):
332-
self._nelm = record.NELM
333341
self._dbf_type_ = record.FTVL
334-
self._dtype = fields.DbfCodeToNumpy[record.FTVL]
335342
return self.__super.init_record(record)
336343

337344
def _read_value(self, record):
@@ -343,12 +350,7 @@ def _read_value(self, record):
343350
return result
344351

345352
def _write_value(self, record, value):
346-
value = numpy.require(value, dtype = self._dtype)
347-
348-
nelm = record.NELM
349353
nord = len(value)
350-
if nord > nelm:
351-
nord = nelm
352354
memmove(
353355
record.BPTR, value.ctypes.data_as(c_void_p),
354356
self._dtype.itemsize * nord)
@@ -360,26 +362,17 @@ def _compare_values(self, value, other):
360362
def _value_to_epics(self, value):
361363
# Ensure we always convert incoming value into numpy array, regardless
362364
# of whether the record has been initialised or not
363-
value = numpy.require(value, dtype = self._dtype)
364-
if value.shape == ():
365-
value.shape = (1,)
366-
assert value.ndim == 1, 'Can\'t write multidimensional arrays'
367-
368-
# Truncate value to fit
369-
if hasattr(self, '_nelm'):
370-
value = value[:self._nelm]
371-
365+
value = _require_waveform(value, self._dtype)
372366
# Because arrays are mutable values it's ever so easy to accidentially
373367
# call set() with a value which subsequently changes. To avoid this
374368
# common class of bug, at the cost of duplicated code and data, here we
375-
# ensure a copy is taken of the value.
376-
return +value
369+
# ensure a copy is taken of the value, after pruning to length.
370+
return +value[:self._nelm]
377371

378372
def _epics_to_value(self, value):
379373
return value
380374

381375
def _value_to_dbr(self, value):
382-
value = numpy.require(value, dtype = self._dtype)
383376
return self._dbf_type_, len(value), value.ctypes.data, value
384377

385378

softioc/fields.py

Lines changed: 0 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -42,19 +42,6 @@
4242
DBF_NOACCESS: c_void_p,
4343
}
4444

45-
# Mapping for record field type to numpy type.
46-
DbfCodeToNumpy = {
47-
DBF_STRING: numpy.dtype('S40'),
48-
DBF_CHAR: numpy.dtype('int8'),
49-
DBF_UCHAR: numpy.dtype('uint8'),
50-
DBF_SHORT: numpy.dtype('int16'),
51-
DBF_USHORT: numpy.dtype('uint16'),
52-
DBF_LONG: numpy.dtype('int32'),
53-
DBF_ULONG: numpy.dtype('uint32'),
54-
DBF_FLOAT: numpy.dtype('float32'),
55-
DBF_DOUBLE: numpy.dtype('float64'),
56-
}
57-
5845
# Mapping from basic DBR_ codes to DBF_ values
5946
DbrToDbfCode = {
6047
DBR_STRING: DBF_STRING,

softioc/pythonSoftIoc.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -23,8 +23,8 @@ def __init__(self, builder, device, name, **fields):
2323
# remaining arguments are passed to the builder. It's a shame we
2424
# have to maintain this separately from the corresponding device list.
2525
DeviceKeywords = [
26-
'on_update', 'on_update_name', 'validate',
27-
'initial_value', 'always_update']
26+
'on_update', 'on_update_name', 'validate', 'always_update',
27+
'initial_value', '_wf_nelm', '_wf_dtype']
2828
device_kargs = {}
2929
for keyword in DeviceKeywords:
3030
if keyword in fields:

0 commit comments

Comments
 (0)