epicsdev 1.0.2__py3-none-any.whl → 2.0.1__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
epicsdev/epicsdev.py CHANGED
@@ -1,15 +1,14 @@
1
1
  """Skeleton and helper functions for creating EPICS PVAccess server"""
2
2
  # pylint: disable=invalid-name
3
- __version__= 'v1.0.2 26-01-18'# --list define directory to save list of PVs.
4
- #TODO: NTEnums do not have structure display
5
- #TODO: Add performance counters to demo.
3
+ __version__= 'v2.0.1 26-01-30'# added mandatory host PV
4
+ #TODO add mandatory PV: host, to identify the server host.
6
5
  #Issue: There is no way in PVAccess to specify if string PV is writable.
7
6
  # As a workaround we append description with suffix ' Features: W' to indicate that.
8
7
 
9
8
  import sys
10
- import time
11
- from time import perf_counter as timer
9
+ from time import time, sleep, strftime, perf_counter as timer
12
10
  import os
11
+ from socket import gethostname
13
12
  from p4p.nt import NTScalar, NTEnum
14
13
  from p4p.nt.enum import ntenum
15
14
  from p4p.server import Server
@@ -17,6 +16,9 @@ from p4p.server.thread import SharedPV
17
16
  from p4p.client.thread import Context
18
17
 
19
18
  #``````````````````Module Storage`````````````````````````````````````````````
19
+ def _serverStateChanged(newState:str):
20
+ """Dummy serverStateChanged function"""
21
+ return
20
22
  class C_():
21
23
  """Storage for module members"""
22
24
  prefix = ''
@@ -24,13 +26,16 @@ class C_():
24
26
  cycle = 0
25
27
  serverState = ''
26
28
  PVs = {}
27
- PVDefs = []
29
+ PVDefs = []
30
+ serverStateChanged = _serverStateChanged
31
+
28
32
  #```````````````````Helper methods````````````````````````````````````````````
29
33
  def serverState():
30
- """Return current server state. That is the value of the server PV, but cached in C_ to avoid unnecessary get() calls."""
34
+ """Return current server state. That is the value of the server PV, but
35
+ cached in C_ to avoid unnecessary get() calls."""
31
36
  return C_.serverState
32
37
  def _printTime():
33
- return time.strftime("%m%d:%H%M%S")
38
+ return strftime("%m%d:%H%M%S")
34
39
  def printi(msg):
35
40
  """Print info message and publish it to status PV."""
36
41
  print(f'inf_@{_printTime()}: {msg}')
@@ -66,24 +71,31 @@ def pvv(pvName:str):
66
71
  return pvobj(pvName).current()
67
72
 
68
73
  def publish(pvName:str, value, ifChanged=False, t=None):
69
- """Publish value to PV. If ifChanged is True, then publish only if the value is different from the current value. If t is not None, then use it as timestamp, otherwise use current time."""
74
+ """Publish value to PV. If ifChanged is True, then publish only if the
75
+ value is different from the current value. If t is not None, then use
76
+ it as timestamp, otherwise use current time."""
77
+ #print(f'Publishing {pvName}')
70
78
  try:
71
79
  pv = pvobj(pvName)
72
80
  except KeyError:
73
- printw(f'PV {pvName} not found. Cannot publish value.')
81
+ print(f'WARNING: PV {pvName} not found. Cannot publish value.')
74
82
  return
75
83
  if t is None:
76
- t = time.time()
84
+ t = time()
77
85
  if not ifChanged or pv.current() != value:
78
86
  pv.post(value, timestamp=t)
79
87
 
80
88
  def SPV(initial, meta='', vtype=None):
81
89
  """Construct SharedPV.
82
- meta is a string with characters W,A,E indicating if the PV is writable, has alarm or it is NTEnum.
83
- vtype should be one of the p4p.nt type definitions (see https://epics-base.github.io/p4p/values.html).
90
+ meta is a string with characters W,R,A,D indicating if the PV is writable,
91
+ has alarm or it is discrete (ENUM).
92
+ vtype should be one of the p4p.nt type definitions
93
+ (see https://epics-base.github.io/p4p/values.html).
84
94
  if vtype is None then the nominal type will be determined automatically.
95
+ initial is the initial value of the PV. It can be a single value or
96
+ a list/array of values (for array PVs).
85
97
  """
86
- typeCode = {
98
+ typeCode = {# mapping from vtype to p4p type code
87
99
  's8':'b', 'u8':'B', 's16':'h', 'u16':'H', 'i32':'i', 'u32':'I', 'i64':'l',
88
100
  'u64':'L', 'f32':'f', 'f64':'d', str:'s',
89
101
  }
@@ -93,21 +105,29 @@ def SPV(initial, meta='', vtype=None):
93
105
  itype = type(firstItem)
94
106
  vtype = {int: 'i32', float: 'f32'}.get(itype,itype)
95
107
  tcode = typeCode[vtype]
96
- if 'E' in meta:
108
+ allowed_chars = 'WRAD'
109
+ discrete = False
110
+ for ch in meta:
111
+ if ch not in allowed_chars:
112
+ printe(f'Unknown meta character {ch} in SPV definition')
113
+ sys.exit(1)
114
+ if 'D' in meta:
115
+ discrete = True
97
116
  initial = {'choices': initial, 'index': 0}
98
117
  nt = NTEnum(display=True, control='W' in meta)
99
118
  else:
100
119
  prefix = 'a' if iterable else ''
101
- nt = NTScalar(prefix+tcode, display=True, control='W' in meta, valueAlarm='A' in meta)
120
+ nt = NTScalar(prefix+tcode, display=True, control='W' in meta,
121
+ valueAlarm='A' in meta)
102
122
  pv = SharedPV(nt=nt, initial=initial)
123
+ # add new attributes.
103
124
  pv.writable = 'W' in meta
125
+ pv.discrete = discrete
104
126
  return pv
105
127
 
106
128
  #``````````````````create_PVs()```````````````````````````````````````````````
107
129
  def _create_PVs(pvDefs):
108
- """Create PVs, using definitions from pvDEfs list. Each definition is a list of the form:
109
- [pvname, description, SPV object, extra], where extra is a dictionary of extra parameters, like setter, units, limits etc. Setter is a function, that will be called when"""
110
- ts = time.time()
130
+ ts = time()
111
131
  for defs in pvDefs:
112
132
  try:
113
133
  pname,desc,spv,extra = defs
@@ -115,15 +135,22 @@ def _create_PVs(pvDefs):
115
135
  printe(f'Invalid PV definition of {defs[0]}')
116
136
  sys.exit(1)
117
137
  ivalue = spv.current()
118
- printv(f'created pv {pname}, initial: {type(ivalue),ivalue}, extra: {extra}')
138
+ printv((f'created pv {pname}, initial: {type(ivalue),ivalue},'
139
+ f'extra: {extra}'))
140
+ key = C_.prefix + pname
141
+ if key in C_.PVs:
142
+ printe(f'Duplicate PV name: {pname}')
143
+ sys.exit(1)
119
144
  C_.PVs[C_.prefix+pname] = spv
120
145
  v = spv._wrap(ivalue, timestamp=ts)
121
146
  if spv.writable:
122
147
  try:
123
- # To indicate that the PV is writable, set control limits to (0,0). Not very elegant, but it works for numerics and enums, not for strings.
148
+ # To indicate that the PV is writable, set control limits to
149
+ # (0,0). Not very elegant, but it works for numerics and enums,
150
+ # not for strings.
124
151
  v['control.limitLow'] = 0
125
152
  v['control.limitHigh'] = 0
126
- except KeyError as e:
153
+ except KeyError:
127
154
  #print(f'control not set for {pname}: {e}')
128
155
  pass
129
156
  if 'ntenum' in str(type(ivalue)):
@@ -140,18 +167,19 @@ def _create_PVs(pvDefs):
140
167
  v[f'valueAlarm.{key}'] = value
141
168
  spv.post(v)
142
169
 
143
- # add new attributes. To my surprise that works!
170
+ # add new attributes.
144
171
  spv.name = pname
145
172
  spv.setter = extra.get('setter')
146
173
 
147
174
  if spv.writable:
148
175
  @spv.put
149
176
  def handle(spv, op):
150
- ct = time.time()
177
+ ct = time()
151
178
  vv = op.value()
152
179
  vr = vv.raw.value
153
180
  current = spv._wrap(spv.current())
154
- # check limits, if they are defined. That will be a good example of using control structure and valueAlarm.
181
+ # check limits, if they are defined. That will be a good
182
+ # example of using control structure and valueAlarm.
155
183
  try:
156
184
  limitLow = current['control.limitLow']
157
185
  limitHigh = current['control.limitHigh']
@@ -162,65 +190,80 @@ def _create_PVs(pvDefs):
162
190
  except KeyError:
163
191
  pass
164
192
  if isinstance(vv, ntenum):
165
- vr = vv
193
+ vr = str(vv)
166
194
  if spv.setter:
167
- spv.setter(vr)
195
+ spv.setter(vr, spv)
168
196
  # value will be updated by the setter, so get it again
169
197
  vr = pvv(spv.name)
170
198
  printv(f'putting {spv.name} = {vr}')
171
199
  spv.post(vr, timestamp=ct) # update subscribers
172
200
  op.done()
173
- #print(f'PV {pv.name} created: {spv}')
174
201
  #,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,
175
202
  #``````````````````Setters
176
- def set_verbosity(level):
203
+ def set_verbose(level, *_):
177
204
  """Set verbosity level for debugging"""
178
205
  C_.verbose = level
179
- publish('verbosity',level)
206
+ printi(f'Setting verbose to {level}')
207
+ publish('verbose',level)
180
208
 
181
- def set_server(state=None):
182
- """Example of the setter for the server PV."""
183
- #printv(f'>set_server({state}), {type(state)}')
184
- if state is None:
185
- state = pvv('server')
186
- printi(f'Setting server state to {state}')
187
- state = str(state)
188
- if state == 'Start':
209
+ def set_server(servState, *_):
210
+ """Example of the setter for the server PV.
211
+ servState can be 'Start', 'Stop', 'Exit' or 'Clear'. If servState is None,
212
+ then get the desired state from the server PV."""
213
+ #printv(f'>set_server({servState}), {type(servState)}')
214
+ if servState is None:
215
+ servState = pvv('server')
216
+ printi(f'Setting server state to {servState}')
217
+ servState = str(servState)
218
+ C_.serverStateChanged(servState)
219
+ if servState == 'Start':
189
220
  printi('Starting the server')
190
- # configure_instrument()
191
- # adopt_local_setting()
192
221
  publish('server','Started')
193
222
  publish('status','Started')
194
- elif state == 'Stop':
223
+ elif servState == 'Stop':
195
224
  printi('server stopped')
196
225
  publish('server','Stopped')
197
226
  publish('status','Stopped')
198
- elif state == 'Exit':
227
+ elif servState == 'Exit':
199
228
  printi('server is exiting')
200
229
  publish('server','Exited')
201
230
  publish('status','Exited')
202
- elif state == 'Clear':
203
- publish('acqCount', 0)
231
+ elif servState == 'Clear':
204
232
  publish('status','Cleared')
205
- # set server to previous state
233
+ # set server to previous servState
206
234
  set_server(C_.serverState)
207
- C_.serverState = state
235
+ return
236
+ C_.serverState = servState
208
237
 
209
238
  def create_PVs(pvDefs=None):
210
- """Creates manadatory PVs and adds PVs specified in pvDefs list"""
239
+ """Creates manadatory PVs and adds PVs specified in pvDefs list.
240
+ Returns dictionary of created PVs.
241
+ Each definition is a list of the form:
242
+ [pvname, description, SPV object, extra], where extra is a dictionary of
243
+ extra parameters.
244
+ Extra parameters can include:
245
+ 'setter' : function to be called on put
246
+ 'units' : string with units
247
+ 'limitLow' : low control limit
248
+ 'limitHigh' : high control limit
249
+ 'format' : format string
250
+ 'valueAlarm': dictionary with valueAlarm parameters, like
251
+ 'lowAlarmLimit', 'highAlarmLimit', etc."""
211
252
  U,LL,LH = 'units','limitLow','limitHigh'
212
253
  C_.PVDefs = [
254
+ ['host', 'Server host name', SPV(gethostname()), {}],
213
255
  ['version', 'Program version', SPV(__version__), {}],
214
- ['status', 'Server status. Features: RWE', SPV('?','W'), {}],
215
- ['server', 'Server control',
216
- SPV('Start Stop Clear Exit Started Stopped Exited'.split(), 'WE'),
256
+ ['status', 'Server status. Features: RWE', SPV('','W'), {}],
257
+ ['server', 'Server control',
258
+ SPV('Start Stop Clear Exit Started Stopped Exited'.split(), 'WD'),
217
259
  {'setter':set_server}],
218
- ['verbosity', 'Debugging verbosity', SPV(0,'W','u8'),
219
- {'setter':set_verbosity}],
260
+ ['verbose', 'Debugging verbosity', SPV(C_.verbose,'W','u8'),
261
+ {'setter':set_verbose, LL:0,LH:3}],
220
262
  ['polling', 'Polling interval', SPV(1.0,'W'), {U:'S', LL:0.001, LH:10.1}],
221
263
  ['cycle', 'Cycle number', SPV(0,'','u32'), {}],
222
264
  ]
223
- # append application's PVs, defined in the pvDefs and create map of providers
265
+ # append application's PVs, defined in the pvDefs and create map of
266
+ # providers
224
267
  if pvDefs is not None:
225
268
  C_.PVDefs += pvDefs
226
269
  _create_PVs(C_.PVDefs)
@@ -232,20 +275,35 @@ def get_externalPV(pvName:str, timeout=0.5):
232
275
  ctxt = Context('pva')
233
276
  return ctxt.get(pvName, timeout=timeout)
234
277
 
235
- def init_epicsdev(prefix:str, pvDefs:list, listDir:str, verbose:str=0):
278
+ def init_epicsdev(prefix:str, pvDefs:list, verbose=0,
279
+ serverStateChanged=None, listDir=None):
236
280
  """Check if no other server is running with the same prefix.
237
281
  Create PVs and return them as a dictionary.
282
+ prefix is a string to be prepended to all PV names.
283
+ pvDefs is a list of PV definitions (see create_PVs()).
284
+ verbose is the verbosity level for debug messages.
285
+ serverStateChanged is a function to be called when the server PV changes.
286
+ The function should have the signature:
287
+ def serverStateChanged(newStatus:str):
288
+ If serverStateChanged is None, then a dummy function is used.
238
289
  The listDir is a directory to save list of all generated PVs,
239
290
  if no directory is given, then </tmp/pvlist/><prefix> is assumed.
240
291
  """
292
+ if not isinstance(verbose, int) or verbose < 0:
293
+ printe('init_epicsdev arguments should be (prefix:str, pvDefs:list, verbose:int, listDir:str)')
294
+ sys.exit(1)
295
+ printi(f'Initializing epicsdev with prefix {prefix}')
241
296
  C_.prefix = prefix
242
297
  C_.verbose = verbose
243
- try:
244
- get_externalPV(prefix+'version')
245
- print(f'ERROR: Server for {prefix} already running. Exiting.')
298
+ if serverStateChanged is not None:# set custom serverStateChanged function
299
+ C_.serverStateChanged = serverStateChanged
300
+ try: # check if server is already running
301
+ host = repr(get_externalPV(prefix+'host')).replace("'",'')
302
+ print(f'ERROR: Server for {prefix} already running at {host}. Exiting.')
246
303
  sys.exit(1)
247
- except TimeoutError:
248
- pass
304
+ except TimeoutError: pass
305
+
306
+ # No existing server found. Creating PVs.
249
307
  pvs = create_PVs(pvDefs)
250
308
  # Save list of PVs to a file, if requested
251
309
  if listDir != '':
@@ -261,7 +319,6 @@ def init_epicsdev(prefix:str, pvDefs:list, listDir:str, verbose:str=0):
261
319
 
262
320
  #``````````````````Demo````````````````````````````````````````````````````````
263
321
  if __name__ == "__main__":
264
- print(f'epicsdev multiadc demo server {__version__}')
265
322
  import numpy as np
266
323
  import argparse
267
324
 
@@ -286,19 +343,22 @@ if __name__ == "__main__":
286
343
  rng = np.random.default_rng(nPatterns)
287
344
  nPoints = 100
288
345
 
289
- def set_recordLength(value):
290
- """Record length have changed. The tAxis should be updated accordingly."""
346
+ def set_recordLength(value, *_):
347
+ """Record length have changed. The tAxis should be updated
348
+ accordingly."""
291
349
  printi(f'Setting tAxis to {value}')
292
350
  publish('tAxis', np.arange(value)*1.E-6)
293
351
  publish('recordLength', value)
294
- set_noise(pvv('noiseLevel')) # Re-initialize noise array, because its size depends on recordLength
352
+ # Re-initialize noise array, because its size depends on recordLength
353
+ set_noise(pvv('noiseLevel'))
295
354
 
296
- def set_noise(level):
355
+ def set_noise(level, *_):
297
356
  """Noise level have changed. Update noise array."""
298
357
  v = float(level)
299
358
  recordLength = pvv('recordLength')
300
359
  ts = timer()
301
- pargs.noise = np.random.normal(scale=0.5*level, size=recordLength+nPatterns)# 45ms/1e6 points
360
+ pargs.noise = np.random.normal(scale=0.5*level,
361
+ size=recordLength+nPatterns)# 45ms/1e6 points
302
362
  printi(f'Noise array[{len(pargs.noise)}] updated with level {v:.4g} V. in {timer()-ts:.4g} S.')
303
363
  publish('noiseLevel', level)
304
364
 
@@ -325,10 +385,13 @@ if __name__ == "__main__":
325
385
  parser = argparse.ArgumentParser(description = __doc__,
326
386
  formatter_class=argparse.ArgumentDefaultsHelpFormatter,
327
387
  epilog=f'{__version__}')
328
- parser.add_argument('-l', '--list', default='', nargs='?', help=
329
- 'Directory to save list of all generated PVs, if no directory is given, then </tmp/pvlist/><prefix> is assumed.')
330
- parser.add_argument('-p', '--prefix', default='epicsDev0:', help=
331
- 'Prefix to be prepended to all PVs')
388
+ parser.add_argument('-d', '--device', default='epicsDev', help=
389
+ 'Device name, the PV name will be <device><index>:')
390
+ parser.add_argument('-i', '--index', default='0', help=
391
+ 'Device index, the PV name will be <device><index>:')
392
+ parser.add_argument('-l', '--list', default='', nargs='?', help=(
393
+ 'Directory to save list of all generated PVs, if no directory is given, '
394
+ 'then </tmp/pvlist/><prefix> is assumed.'))
332
395
  # The rest of options are not essential, they can be controlled at runtime using PVs.
333
396
  parser.add_argument('-n', '--npoints', type=int, default=nPoints, help=
334
397
  'Number of points in the waveform')
@@ -338,9 +401,11 @@ if __name__ == "__main__":
338
401
  print(pargs)
339
402
 
340
403
  # Initialize epicsdev and PVs
341
- PVs = init_epicsdev(pargs.prefix, myPVDefs(), pargs.list, pargs.verbose)
404
+ pargs.prefix = f'{pargs.device}{pargs.index}:'
405
+ PVs = init_epicsdev(pargs.prefix, myPVDefs(), pargs.verbose, None, pargs.list)
342
406
 
343
- # Initialize the device, using pargs if needed. That can be used to set the number of points in the waveform, for example.
407
+ # Initialize the device using pargs if needed. That can be used to set
408
+ # the number of points in the waveform, for example.
344
409
  init(pargs.npoints)
345
410
 
346
411
  # Start the Server. Use your set_server, if needed.
@@ -355,5 +420,5 @@ if __name__ == "__main__":
355
420
  break
356
421
  if not state.startswith('Stop'):
357
422
  poll()
358
- time.sleep(pvv("polling"))
423
+ sleep(pvv("polling"))
359
424
  printi('Server is exited')
epicsdev/multiadc.py ADDED
@@ -0,0 +1,165 @@
1
+ """Simulated multi-channel ADC device server using epicsdev module."""
2
+ # pylint: disable=invalid-name
3
+ __version__= 'v0.0.2 26-01-23'# refactored, adjusted for epicdev 2.0.1
4
+
5
+ import sys
6
+ import time
7
+ from time import perf_counter as timer
8
+ import numpy as np
9
+ import argparse
10
+
11
+ from .epicsdev import Server, Context, init_epicsdev, serverState, publish
12
+ from .epicsdev import pvv, printi, printv, SPV, set_server
13
+
14
+
15
+ def myPVDefs():
16
+ """Example of PV definitions"""
17
+ SET,U,LL,LH = 'setter','units','limitLow','limitHigh'
18
+ alarm = {'valueAlarm':{'lowAlarmLimit':-9., 'highAlarmLimit':9.}}
19
+ pvDefs = [ # device-specific PVs
20
+ ['externalControl', 'Name of external PV, which controls the server',
21
+ SPV('Start Stop Clear Exit Started Stopped Exited'.split(), 'WD'), {}],
22
+ ['noiseLevel', 'Noise amplitude', SPV(1.E-4,'W'), {SET:set_noise, U:'V'}],
23
+ ['tAxis', 'Full scale of horizontal axis', SPV([0.]), {U:'S'}],
24
+ ['recordLength','Max number of points', SPV(100,'W','u32'),
25
+ {LL:4,LH:1000000, SET:set_recordLength}],
26
+ ['alarm', 'PV with alarm', SPV(0,'WA'), {U:'du',**alarm}],
27
+ ]
28
+
29
+ # Templates for channel-related PVs. Important: SPV cannot be used in this list!
30
+ ChannelTemplates = [
31
+ ['c0$VoltsPerDiv', 'Vertical scale', (1E-3,'W'), {U:'V/du'}],
32
+ #['c0$VoltOffset', 'Vertical offset', (1E-3,), {U:'V/du'}],
33
+ ['c0$Waveform', 'Waveform array', ([0.],), {U:'du'}],
34
+ ['c0$Mean', 'Mean of the waveform', (0.,'A'), {U:'du'}],
35
+ ['c0$Peak2Peak','Peak-to-peak amplitude', (0.,'A'), {U:'du',**alarm}],
36
+ ]
37
+ # extend PvDefs with channel-related PVs
38
+ for ch in range(pargs.channels):
39
+ for pvdef in ChannelTemplates:
40
+ newpvdef = pvdef.copy()
41
+ newpvdef[0] = pvdef[0].replace('0$',f'{ch+1:02}')
42
+ newpvdef[2] = SPV(*pvdef[2])
43
+ pvDefs.append(newpvdef)
44
+ return pvDefs
45
+
46
+ #``````````````````Module constants
47
+ nPatterns = 100 # number of waveform patterns.
48
+ rng = np.random.default_rng(nPatterns)
49
+
50
+ #``````````````````Setter functions for PVs```````````````````````````````````
51
+ def set_recordLength(value):
52
+ """Record length have changed. The tAxis should be updated accordingly."""
53
+ printi(f'Setting tAxis to {value}')
54
+ publish('tAxis', np.arange(value)*1.E-6)
55
+ publish('recordLength', value)
56
+ # Re-initialize noise array, because its size depends on recordLength
57
+ set_noise(pvv('noiseLevel'))
58
+
59
+ def set_noise(level):
60
+ """Noise level have changed. Update noise array."""
61
+ v = float(level)
62
+ recordLength = pvv('recordLength')
63
+ ts = timer()
64
+
65
+ pargs.noise = np.random.normal(scale=0.5*level, size=recordLength+nPatterns)# 45ms/1e6 points
66
+ printi(f'Noise array[{len(pargs.noise)}] updated with level {v:.4g} V. in {timer()-ts:.4g} S.')
67
+ publish('noiseLevel', level)
68
+
69
+ def set_externalControl(value):
70
+ """External control PV have changed. Control the server accordingly."""
71
+ pvname = str(value)
72
+ if pvname in (None,'0'):
73
+ print('External control is not activated.')
74
+ return
75
+ printi(f'External control PV: {pvname}')
76
+ ctxt = Context('pva')
77
+ try:
78
+ r = ctxt.get(pvname, timeout=0.5)
79
+ except TimeoutError:
80
+ printi(f'Cannot connect to external control PV {pvname}.')
81
+ sys.exit(1)
82
+ #,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,
83
+ def serverStateChanged(newState:str):
84
+ """Start device function called when server is started"""
85
+ if newState == 'Start':
86
+ printi('start_device called')
87
+ elif newState == 'Stop':
88
+ printi('stop_device called')
89
+ elif newState == 'Clear':
90
+ printi('clear_device called')
91
+ publish('cycle', 0)
92
+
93
+ def init(recordLength):
94
+ """Testing function. Do not use in production code."""
95
+ set_recordLength(recordLength)
96
+ #set_externalControl(pargs.prefix + pargs.external)
97
+
98
+ def poll():
99
+ """Example of polling function"""
100
+ #pattern = C_.cycle % nPatterns# produces sliding
101
+ cycle = pvv('cycle')
102
+ printv(f'cycle {repr(cycle)}')
103
+ publish('cycle', cycle + 1)
104
+ for ch in range(pargs.channels):
105
+ pattern = rng.integers(0, nPatterns)
106
+ chstr = f'c{ch+1:02}'
107
+ wf = pargs.noise[pattern:pattern+pvv('recordLength')].copy()
108
+ #print(f'ch{ch}, {pattern}: {wf[0], wf.sum(), wf.mean(), np.mean(wf)}')
109
+ wf /= pvv(f'{chstr}VoltsPerDiv')
110
+ #wf += pvv(f'{chstr}Offset')
111
+ wf += ch
112
+ publish(f'{chstr}Waveform', list(wf))
113
+ publish(f'{chstr}Peak2Peak', np.ptp(wf))
114
+ publish(f'{chstr}Mean', np.mean(wf))
115
+
116
+ # Argument parsing
117
+ parser = argparse.ArgumentParser(description = __doc__,
118
+ formatter_class=argparse.ArgumentDefaultsHelpFormatter,
119
+ epilog=f'{__version__}')
120
+ parser.add_argument('-c', '--channels', type=int, default=6, help=
121
+ 'Number of channels per device')
122
+ parser.add_argument('-e', '--external', help=
123
+ 'Name of external PV, which controls the server, if 0 then it will be <device>0:')
124
+ parser.add_argument('-l', '--list', default=None, nargs='?', help=
125
+ 'Directory to save list of all generated PVs, if None, then </tmp/pvlist/><prefix> is assumed.')
126
+ parser.add_argument('-d', '--device', default='multiadc', help=
127
+ 'Device name, the PV name will be <device><index>:')
128
+ parser.add_argument('-i', '--index', default='0', help=
129
+ 'Device index, the PV name will be <device><index>:')
130
+ # The rest of arguments are not essential, they can be changed at runtime using PVs.
131
+ parser.add_argument('-n', '--npoints', type=int, default=100, help=
132
+ 'Number of points in the waveform')
133
+ parser.add_argument('-v', '--verbose', action='count', default=0, help=
134
+ 'Show more log messages (-vv: show even more)')
135
+ pargs = parser.parse_args()
136
+ print(f'pargs: {pargs}')
137
+
138
+ # Initialize epicsdev and PVs
139
+ pargs.prefix = f'{pargs.device}{pargs.index}:'
140
+ PVs = init_epicsdev(pargs.prefix, myPVDefs(), pargs.verbose,
141
+ serverStateChanged, pargs.list)
142
+ # if pargs.list != '':
143
+ # print('List of PVs:')
144
+ # for _pvname in PVs:
145
+ # print(_pvname)
146
+ printi(f'Hosting {len(PVs)} PVs')
147
+
148
+ # Initialize the device, using pargs if needed.
149
+ # That can be used to set the number of points in the waveform, for example.
150
+ init(pargs.npoints)
151
+
152
+ # Start the Server. Use your set_server, if needed.
153
+ set_server('Start')
154
+
155
+ #``````````````````Main loop``````````````````````````````````````````````````
156
+ server = Server(providers=[PVs])
157
+ printi(f'Server started with polling interval {repr(pvv("polling"))} S.')
158
+ while True:
159
+ state = serverState()
160
+ if state.startswith('Exit'):
161
+ break
162
+ if not state.startswith('Stop'):
163
+ poll()
164
+ time.sleep(pvv("polling"))
165
+ printi('Server is exited')
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: epicsdev
3
- Version: 1.0.2
3
+ Version: 2.0.1
4
4
  Summary: Helper module for creating EPICS PVAccess servers using p4p
5
5
  Project-URL: Homepage, https://github.com/ASukhanov/epicsdev
6
6
  Project-URL: Bug Tracker, https://github.com/ASukhanov/epicsdev
@@ -19,7 +19,7 @@ Helper module for creating EPICS PVAccess servers.
19
19
  Demo:
20
20
  ```
21
21
  python pip install epicsdev
22
- python -m epicsdev.epicsdev -l
22
+ python -m epicsdev.epicsdev
23
23
  ```
24
24
 
25
25
  To control and plot:
@@ -28,4 +28,18 @@ python pip install pypeto,pvplot
28
28
  python -m pypeto -c config -f epicsdev
29
29
  ```
30
30
 
31
+ ## Multi-channel waveform generator
32
+ Module **epicdev.multiadc** can generate large amount of data for stress-testing
33
+ the EPICS environment. For example the following command will generate 100 of
34
+ 1000-pont noisy waveforms and 300 of scalar parameters.
35
+ ```
36
+ python -m epicsdev.multiadc -c100 -n1000
37
+ ```
38
+ The GUI for monitoring:<br>
39
+ ```python -m pypeto -c config -f multiadc```
40
+
41
+ The graphs should look like this:
42
+ [control page](docs/epicsdev_pypet.png),
43
+ [plots](docs/epicsdev_pvplot.jpg).
44
+
31
45
 
@@ -0,0 +1,7 @@
1
+ epicsdev/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
2
+ epicsdev/epicsdev.py,sha256=2hNtiqiD491XwudLpwy8fg85UO7etzttIY3SZs_FMQ4,16969
3
+ epicsdev/multiadc.py,sha256=r7vC9eG9CRRlW9WyJEy3h7A435WqrDn7KqpQXNUC6xA,6619
4
+ epicsdev-2.0.1.dist-info/METADATA,sha256=2_tD2YFalnTHU5D_Y7JM9PZIOqxPIv8GIQ-R2IQKkFA,1270
5
+ epicsdev-2.0.1.dist-info/WHEEL,sha256=WLgqFyCfm_KASv4WHyYy0P3pM_m7J5L9k2skdKLirC8,87
6
+ epicsdev-2.0.1.dist-info/licenses/LICENSE,sha256=qj3cUKUrX4oXTb0NwuJQ44ThYDEMUfOeIjw9kkT6Qck,1072
7
+ epicsdev-2.0.1.dist-info/RECORD,,
@@ -1,6 +0,0 @@
1
- epicsdev/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
2
- epicsdev/epicsdev.py,sha256=Cr4yUBCcCJVQiREgiultFJlUqWIHWVlewtzI45hHyNs,14598
3
- epicsdev-1.0.2.dist-info/METADATA,sha256=LZvfLp-nbvHZso5JxY_w76cNcjh-1sZaHU8oGEoDcbE,786
4
- epicsdev-1.0.2.dist-info/WHEEL,sha256=WLgqFyCfm_KASv4WHyYy0P3pM_m7J5L9k2skdKLirC8,87
5
- epicsdev-1.0.2.dist-info/licenses/LICENSE,sha256=qj3cUKUrX4oXTb0NwuJQ44ThYDEMUfOeIjw9kkT6Qck,1072
6
- epicsdev-1.0.2.dist-info/RECORD,,