epicsdev 2.0.1__py3-none-any.whl → 2.1.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,12 +1,12 @@
1
1
  """Skeleton and helper functions for creating EPICS PVAccess server"""
2
2
  # pylint: disable=invalid-name
3
- __version__= 'v2.0.1 26-01-30'# added mandatory host PV
4
- #TODO add mandatory PV: host, to identify the server host.
3
+ __version__= 'v2.1.1 26-02-05'# sleep() returns False if a periodic update occurred. Simplified waveform randomization.
5
4
  #Issue: There is no way in PVAccess to specify if string PV is writable.
6
5
  # As a workaround we append description with suffix ' Features: W' to indicate that.
7
6
 
8
7
  import sys
9
- from time import time, sleep, strftime, perf_counter as timer
8
+ import time
9
+ from time import perf_counter as timer
10
10
  import os
11
11
  from socket import gethostname
12
12
  from p4p.nt import NTScalar, NTEnum
@@ -15,6 +15,8 @@ from p4p.server import Server
15
15
  from p4p.server.thread import SharedPV
16
16
  from p4p.client.thread import Context
17
17
 
18
+ PeriodicUpdateInterval = 10. # seconds
19
+
18
20
  #``````````````````Module Storage`````````````````````````````````````````````
19
21
  def _serverStateChanged(newState:str):
20
22
  """Dummy serverStateChanged function"""
@@ -28,6 +30,10 @@ class C_():
28
30
  PVs = {}
29
31
  PVDefs = []
30
32
  serverStateChanged = _serverStateChanged
33
+ lastCycleTime = timer()
34
+ lastUpdateTime = 0.
35
+ cycleTimeSum = 0.
36
+ cyclesAfterUpdate = 0
31
37
 
32
38
  #```````````````````Helper methods````````````````````````````````````````````
33
39
  def serverState():
@@ -35,7 +41,7 @@ def serverState():
35
41
  cached in C_ to avoid unnecessary get() calls."""
36
42
  return C_.serverState
37
43
  def _printTime():
38
- return strftime("%m%d:%H%M%S")
44
+ return time.strftime("%m%d:%H%M%S")
39
45
  def printi(msg):
40
46
  """Print info message and publish it to status PV."""
41
47
  print(f'inf_@{_printTime()}: {msg}')
@@ -81,7 +87,7 @@ def publish(pvName:str, value, ifChanged=False, t=None):
81
87
  print(f'WARNING: PV {pvName} not found. Cannot publish value.')
82
88
  return
83
89
  if t is None:
84
- t = time()
90
+ t = time.time()
85
91
  if not ifChanged or pv.current() != value:
86
92
  pv.post(value, timestamp=t)
87
93
 
@@ -127,7 +133,7 @@ def SPV(initial, meta='', vtype=None):
127
133
 
128
134
  #``````````````````create_PVs()```````````````````````````````````````````````
129
135
  def _create_PVs(pvDefs):
130
- ts = time()
136
+ ts = time.time()
131
137
  for defs in pvDefs:
132
138
  try:
133
139
  pname,desc,spv,extra = defs
@@ -174,7 +180,7 @@ def _create_PVs(pvDefs):
174
180
  if spv.writable:
175
181
  @spv.put
176
182
  def handle(spv, op):
177
- ct = time()
183
+ ct = time.time()
178
184
  vv = op.value()
179
185
  vr = vv.raw.value
180
186
  current = spv._wrap(spv.current())
@@ -259,8 +265,12 @@ def create_PVs(pvDefs=None):
259
265
  {'setter':set_server}],
260
266
  ['verbose', 'Debugging verbosity', SPV(C_.verbose,'W','u8'),
261
267
  {'setter':set_verbose, LL:0,LH:3}],
262
- ['polling', 'Polling interval', SPV(1.0,'W'), {U:'S', LL:0.001, LH:10.1}],
263
- ['cycle', 'Cycle number', SPV(0,'','u32'), {}],
268
+ ['sleep', 'Pause in the main loop, it could be useful for throttling the data output',
269
+ SPV(1.0,'W'), {U:'S', LL:0.001, LH:10.1}],
270
+ ['cycle', 'Cycle number, published every {PeriodicUpdateInterval} S.',
271
+ SPV(0,'','u32'), {}],
272
+ ['cycleTime','Average cycle time including sleep, published every {PeriodicUpdateInterval} S',
273
+ SPV(0.), {U:'S'}],
264
274
  ]
265
275
  # append application's PVs, defined in the pvDefs and create map of
266
276
  # providers
@@ -301,7 +311,8 @@ def init_epicsdev(prefix:str, pvDefs:list, verbose=0,
301
311
  host = repr(get_externalPV(prefix+'host')).replace("'",'')
302
312
  print(f'ERROR: Server for {prefix} already running at {host}. Exiting.')
303
313
  sys.exit(1)
304
- except TimeoutError: pass
314
+ except TimeoutError:
315
+ pass
305
316
 
306
317
  # No existing server found. Creating PVs.
307
318
  pvs = create_PVs(pvDefs)
@@ -315,8 +326,33 @@ def init_epicsdev(prefix:str, pvDefs:list, verbose=0,
315
326
  with open(filepath, 'w', encoding="utf-8") as f:
316
327
  for _pvname in pvs:
317
328
  f.write(_pvname + '\n')
329
+ printi(f'Hosting {len(pvs)} PVs')
318
330
  return pvs
319
331
 
332
+ def sleep():
333
+ """Sleep function to be called in the main loop. It updates cycleTime PV
334
+ and sleeps for the time specified in sleep PV.
335
+ Returns False if a periodic update occurred.
336
+ """
337
+ time.sleep(pvv('sleep'))
338
+ tnow = timer()
339
+ C_.cycleTimeSum += tnow - C_.lastCycleTime
340
+ C_.lastCycleTime = tnow
341
+ C_.cyclesAfterUpdate += 1
342
+ C_.cycle += 1
343
+ printv(f'cycle {C_.cycle}')
344
+ sleeping = True
345
+ if tnow - C_.lastUpdateTime > PeriodicUpdateInterval:
346
+ avgCycleTime = C_.cycleTimeSum / C_.cyclesAfterUpdate
347
+ printv(f'Average cycle time: {avgCycleTime:.6f} S.')
348
+ publish('cycle', C_.cycle)
349
+ publish('cycleTime', avgCycleTime)
350
+ C_.lastUpdateTime = tnow
351
+ C_.cycleTimeSum = 0.
352
+ C_.cyclesAfterUpdate = 0
353
+ sleeping = False
354
+ return sleeping
355
+
320
356
  #``````````````````Demo````````````````````````````````````````````````````````
321
357
  if __name__ == "__main__":
322
358
  import numpy as np
@@ -327,20 +363,20 @@ if __name__ == "__main__":
327
363
  SET,U,LL,LH = 'setter','units','limitLow','limitHigh'
328
364
  alarm = {'valueAlarm':{'lowAlarmLimit':-9., 'highAlarmLimit':9.}}
329
365
  return [ # device-specific PVs
330
- ['noiseLevel', 'Noise amplitude', SPV(1.E-6,'W'), {SET:set_noise, U:'V'}],
366
+ ['noiseLevel', 'Noise amplitude', SPV(1.,'W'), {U:'V'}],
331
367
  ['tAxis', 'Full scale of horizontal axis', SPV([0.]), {U:'S'}],
332
368
  ['recordLength','Max number of points', SPV(100,'W','u32'),
333
369
  {LL:4,LH:1000000, SET:set_recordLength}],
334
- ['ch1Offset', 'Offset', SPV(0.,'W'), {U:'du'}],
335
- ['ch1VoltsPerDiv', 'Vertical scale', SPV(1E-3,'W'), {U:'V/du'}],
336
- ['ch1Waveform', 'Waveform array', SPV([0.]), {U:'du'}],
337
- ['ch1Mean', 'Mean of the waveform', SPV(0.,'A'), {U:'du'}],
338
- ['ch1Peak2Peak','Peak-to-peak amplitude', SPV(0.,'A'), {U:'du',**alarm}],
370
+ ['throughput', 'Performance metrics, points per second', SPV(0.), {U:'Mpts/s'}],
371
+ ['c01Offset', 'Offset', SPV(0.,'W'), {U:'du'}],
372
+ ['c01VoltsPerDiv', 'Vertical scale', SPV(0.1,'W'), {U:'V/du'}],
373
+ ['c01Waveform', 'Waveform array', SPV([0.]), {U:'du'}],
374
+ ['c01Mean', 'Mean of the waveform', SPV(0.,'A'), {U:'du'}],
375
+ ['c01Peak2Peak','Peak-to-peak amplitude', SPV(0.,'A'), {U:'du',**alarm}],
339
376
  ['alarm', 'PV with alarm', SPV(0,'WA'), {U:'du',**alarm}],
340
377
  ]
341
- nPatterns = 100 # number of waveform patterns.
342
378
  pargs = None
343
- rng = np.random.default_rng(nPatterns)
379
+ rng = np.random.default_rng()
344
380
  nPoints = 100
345
381
 
346
382
  def set_recordLength(value, *_):
@@ -349,37 +385,23 @@ if __name__ == "__main__":
349
385
  printi(f'Setting tAxis to {value}')
350
386
  publish('tAxis', np.arange(value)*1.E-6)
351
387
  publish('recordLength', value)
352
- # Re-initialize noise array, because its size depends on recordLength
353
- set_noise(pvv('noiseLevel'))
354
-
355
- def set_noise(level, *_):
356
- """Noise level have changed. Update noise array."""
357
- v = float(level)
358
- recordLength = pvv('recordLength')
359
- ts = timer()
360
- pargs.noise = np.random.normal(scale=0.5*level,
361
- size=recordLength+nPatterns)# 45ms/1e6 points
362
- printi(f'Noise array[{len(pargs.noise)}] updated with level {v:.4g} V. in {timer()-ts:.4g} S.')
363
- publish('noiseLevel', level)
364
388
 
365
389
  def init(recordLength):
366
- """Testing function. Do not use in production code."""
390
+ """Example of device initialization function"""
367
391
  set_recordLength(recordLength)
368
392
  #set_noise(pvv('noiseLevel')) # already called from set_recordLength
369
393
 
370
394
  def poll():
371
- """Example of polling function"""
372
- #pattern = C_.cycle % nPatterns# produces sliding
373
- pattern = rng.integers(0, nPatterns)
374
- cycle = pvv('cycle')
375
- printv(f'cycle {repr(cycle)}')
376
- publish('cycle', cycle + 1)
377
- wf = pargs.noise[pattern:pattern+pvv('recordLength')].copy()
378
- wf /= pvv('ch1VoltsPerDiv')
379
- wf += pvv('ch1Offset')
380
- publish('ch1Waveform', wf)
381
- publish('ch1Peak2Peak', np.ptp(wf))
382
- publish('ch1Mean', np.mean(wf))
395
+ """Example of polling function. Called every cycle when server is running."""
396
+ #ts = timer()
397
+ wf = rng.random(pvv('recordLength'))*pvv('noiseLevel')# it takes 5ms for 1M points
398
+ wf /= pvv('c01VoltsPerDiv')
399
+ wf += pvv('c01Offset')
400
+ #print(f'Waveform updated in {timer()-ts:.6g} S.')
401
+ publish('c01Waveform', wf)
402
+ publish('c01Peak2Peak', np.ptp(wf))
403
+ publish('c01Mean', np.mean(wf))
404
+ #print(f'Polling completed in {timer()-ts:.6g} S.')
383
405
 
384
406
  # Argument parsing
385
407
  parser = argparse.ArgumentParser(description = __doc__,
@@ -389,7 +411,7 @@ if __name__ == "__main__":
389
411
  'Device name, the PV name will be <device><index>:')
390
412
  parser.add_argument('-i', '--index', default='0', help=
391
413
  'Device index, the PV name will be <device><index>:')
392
- parser.add_argument('-l', '--list', default='', nargs='?', help=(
414
+ parser.add_argument('-l', '--list', nargs='?', help=(
393
415
  'Directory to save list of all generated PVs, if no directory is given, '
394
416
  'then </tmp/pvlist/><prefix> is assumed.'))
395
417
  # The rest of options are not essential, they can be controlled at runtime using PVs.
@@ -413,12 +435,16 @@ if __name__ == "__main__":
413
435
 
414
436
  # Main loop
415
437
  server = Server(providers=[PVs])
416
- printi(f'Server started with polling interval {repr(pvv("polling"))} S.')
438
+ printi(f'Server started. Sleeping per cycle: {repr(pvv("sleep"))} S.')
417
439
  while True:
418
440
  state = serverState()
419
441
  if state.startswith('Exit'):
420
442
  break
421
443
  if not state.startswith('Stop'):
422
444
  poll()
423
- sleep(pvv("polling"))
445
+ if not sleep():# Sleep and update performance metrics periodically
446
+ if not state.startswith('Stop'):
447
+ pointsPerSecond = len(pvv('c01Waveform'))/(pvv('cycleTime')-pvv('sleep'))/1.E6
448
+ publish('throughput', round(pointsPerSecond,6))
449
+ printv(f'periodic update. Performance: {pointsPerSecond:.3g} Mpts/s')
424
450
  printi('Server is exited')
epicsdev/multiadc.py CHANGED
@@ -1,15 +1,14 @@
1
1
  """Simulated multi-channel ADC device server using epicsdev module."""
2
2
  # pylint: disable=invalid-name
3
- __version__= 'v0.0.2 26-01-23'# refactored, adjusted for epicdev 2.0.1
3
+ __version__= 'v2.1.1 26-02-04'# added timing, throughput and c0$VoltOffset PVs
4
4
 
5
5
  import sys
6
- import time
7
6
  from time import perf_counter as timer
8
- import numpy as np
9
7
  import argparse
8
+ import numpy as np
10
9
 
11
10
  from .epicsdev import Server, Context, init_epicsdev, serverState, publish
12
- from .epicsdev import pvv, printi, printv, SPV, set_server
11
+ from .epicsdev import pvv, printi, printv, SPV, set_server, sleep
13
12
 
14
13
 
15
14
  def myPVDefs():
@@ -17,19 +16,23 @@ def myPVDefs():
17
16
  SET,U,LL,LH = 'setter','units','limitLow','limitHigh'
18
17
  alarm = {'valueAlarm':{'lowAlarmLimit':-9., 'highAlarmLimit':9.}}
19
18
  pvDefs = [ # device-specific PVs
19
+ ['channels', 'Number of device channels', SPV(pargs.channels), {}],
20
20
  ['externalControl', 'Name of external PV, which controls the server',
21
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'}],
22
+ ['noiseLevel', 'Noise amplitude', SPV(0.05,'W'), {U:'V'}],
23
23
  ['tAxis', 'Full scale of horizontal axis', SPV([0.]), {U:'S'}],
24
24
  ['recordLength','Max number of points', SPV(100,'W','u32'),
25
25
  {LL:4,LH:1000000, SET:set_recordLength}],
26
26
  ['alarm', 'PV with alarm', SPV(0,'WA'), {U:'du',**alarm}],
27
+ #``````````````````Auxiliary PVs
28
+ ['timing', 'Elapsed time for waveform generation, publishing, total]', SPV([0.]), {U:'S'}],
29
+ ['throughput', 'Total number of points processed per second', SPV(0.), {U:'Mpts/s'}],
27
30
  ]
28
31
 
29
32
  # Templates for channel-related PVs. Important: SPV cannot be used in this list!
30
33
  ChannelTemplates = [
31
- ['c0$VoltsPerDiv', 'Vertical scale', (1E-3,'W'), {U:'V/du'}],
32
- #['c0$VoltOffset', 'Vertical offset', (1E-3,), {U:'V/du'}],
34
+ ['c0$VoltsPerDiv', 'Vertical scale', (0.1,'W'), {U:'V/du'}],
35
+ ['c0$VoltOffset', 'Vertical offset', (0.,'W'), {U:'V'}],
33
36
  ['c0$Waveform', 'Waveform array', ([0.],), {U:'du'}],
34
37
  ['c0$Mean', 'Mean of the waveform', (0.,'A'), {U:'du'}],
35
38
  ['c0$Peak2Peak','Peak-to-peak amplitude', (0.,'A'), {U:'du',**alarm}],
@@ -43,30 +46,20 @@ def myPVDefs():
43
46
  pvDefs.append(newpvdef)
44
47
  return pvDefs
45
48
 
46
- #``````````````````Module constants
47
- nPatterns = 100 # number of waveform patterns.
48
- rng = np.random.default_rng(nPatterns)
49
+ #``````````````````Module attributes
50
+ rng = np.random.default_rng()
51
+ ElapsedTime = {'waveform': 0., 'publish': 0., 'poll': 0.}
52
+ class C_():
53
+ cyclesSinceUpdate = 0
49
54
 
50
55
  #``````````````````Setter functions for PVs```````````````````````````````````
51
- def set_recordLength(value):
56
+ def set_recordLength(value, *_):
52
57
  """Record length have changed. The tAxis should be updated accordingly."""
53
58
  printi(f'Setting tAxis to {value}')
54
59
  publish('tAxis', np.arange(value)*1.E-6)
55
60
  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
61
 
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):
62
+ def set_externalControl(value, *_):
70
63
  """External control PV have changed. Control the server accordingly."""
71
64
  pvname = str(value)
72
65
  if pvname in (None,'0'):
@@ -91,27 +84,45 @@ def serverStateChanged(newState:str):
91
84
  publish('cycle', 0)
92
85
 
93
86
  def init(recordLength):
94
- """Testing function. Do not use in production code."""
87
+ """Device initialization function"""
95
88
  set_recordLength(recordLength)
89
+ # Set offset of each channel = channel index
90
+ for ch in range(pargs.channels):
91
+ publish(f'c{ch+1:02}VoltOffset', ch)
96
92
  #set_externalControl(pargs.prefix + pargs.external)
93
+ publish('sleep', pargs.sleep)
97
94
 
98
95
  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)
96
+ """Device polling function, called every cycle when server is running"""
97
+ C_.cyclesSinceUpdate += 1
98
+ ts0 = timer()
104
99
  for ch in range(pargs.channels):
105
- pattern = rng.integers(0, nPatterns)
100
+ ts1 = timer()
106
101
  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))
102
+ rwf = rng.random(pvv('recordLength'))*pvv('noiseLevel')
103
+ wf = rwf/pvv(f'{chstr}VoltsPerDiv') + pvv(f'{chstr}VoltOffset')# the time is comparable with rng.random
104
+ ts2 = timer()
105
+ ElapsedTime['waveform'] += ts2 - ts1
106
+ #print(f'ElapsedTime: {C_.cyclesSinceUpdate, ElapsedTime["waveform"]}')
107
+ publish(f'{chstr}Waveform', wf)
113
108
  publish(f'{chstr}Peak2Peak', np.ptp(wf))
114
109
  publish(f'{chstr}Mean', np.mean(wf))
110
+ ElapsedTime['publish'] += timer() - ts2
111
+ ElapsedTime['poll'] += timer() - ts0
112
+
113
+ def periodic_update():
114
+ """Perform periodic update"""
115
+ #printi(f'periodic update for {C_.cyclesSinceUpdate} cycles: {ElapsedTime}')
116
+ times = [(round(i/C_.cyclesSinceUpdate,6)) for i in ElapsedTime.values()]
117
+ publish('timing', times)
118
+ C_.cyclesSinceUpdate = 0
119
+ for key in ElapsedTime:
120
+ ElapsedTime[key] = 0.
121
+ pointsPerSecond = len(pvv('tAxis'))/(pvv('cycleTime')-pvv('sleep'))/1.E6
122
+ pointsPerSecond *= pvv('channels')
123
+ publish('throughput', round(pointsPerSecond,6))
124
+ printv(f'periodic update. Performance: {pointsPerSecond:.3g} Mpts/s')
125
+
115
126
 
116
127
  # Argument parsing
117
128
  parser = argparse.ArgumentParser(description = __doc__,
@@ -130,6 +141,8 @@ parser.add_argument('-i', '--index', default='0', help=
130
141
  # The rest of arguments are not essential, they can be changed at runtime using PVs.
131
142
  parser.add_argument('-n', '--npoints', type=int, default=100, help=
132
143
  'Number of points in the waveform')
144
+ parser.add_argument('-s', '--sleep', type=float, default=1.0, help=
145
+ 'Sleep time per cycle')
133
146
  parser.add_argument('-v', '--verbose', action='count', default=0, help=
134
147
  'Show more log messages (-vv: show even more)')
135
148
  pargs = parser.parse_args()
@@ -139,11 +152,6 @@ print(f'pargs: {pargs}')
139
152
  pargs.prefix = f'{pargs.device}{pargs.index}:'
140
153
  PVs = init_epicsdev(pargs.prefix, myPVDefs(), pargs.verbose,
141
154
  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
155
 
148
156
  # Initialize the device, using pargs if needed.
149
157
  # That can be used to set the number of points in the waveform, for example.
@@ -154,12 +162,13 @@ set_server('Start')
154
162
 
155
163
  #``````````````````Main loop``````````````````````````````````````````````````
156
164
  server = Server(providers=[PVs])
157
- printi(f'Server started with polling interval {repr(pvv("polling"))} S.')
165
+ printi(f'Server started. Sleeping per cycle: {float(pvv("sleep")):.3f} S.')
158
166
  while True:
159
167
  state = serverState()
160
168
  if state.startswith('Exit'):
161
169
  break
162
170
  if not state.startswith('Stop'):
163
171
  poll()
164
- time.sleep(pvv("polling"))
165
- printi('Server is exited')
172
+ if not sleep():
173
+ periodic_update()
174
+ printi('Server has exited')
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: epicsdev
3
- Version: 2.0.1
3
+ Version: 2.1.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
@@ -30,10 +30,10 @@ python -m pypeto -c config -f epicsdev
30
30
 
31
31
  ## Multi-channel waveform generator
32
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.
33
+ the EPICS environment. For example the following command will generate 10000 of
34
+ 100-pont noisy waveforms and 40000 of scalar parameters per second.
35
35
  ```
36
- python -m epicsdev.multiadc -c100 -n1000
36
+ python -m epicsdev.multiadc -s0.1 -c10000 -n100
37
37
  ```
38
38
  The GUI for monitoring:<br>
39
39
  ```python -m pypeto -c config -f multiadc```
@@ -42,4 +42,4 @@ The graphs should look like this:
42
42
  [control page](docs/epicsdev_pypet.png),
43
43
  [plots](docs/epicsdev_pvplot.jpg).
44
44
 
45
-
45
+ Example of [Phoebus display](docs/phoebus_epicsdev.jpg), as defined in config/epicsdev.bob.
@@ -0,0 +1,7 @@
1
+ epicsdev/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
2
+ epicsdev/epicsdev.py,sha256=dVPuuQ0xXF8ibw28DpvxQfkG_9k7WubNp_9rWXRvTVw,17970
3
+ epicsdev/multiadc.py,sha256=2sg_VmIfylG5XHJzx5bgi_Bb9VPiX2TtyI-8JuAiBmQ,7219
4
+ epicsdev-2.1.1.dist-info/METADATA,sha256=2tMnntNmRcksDUO0fAH9PCrmICCWS4ad7GRcGwnnAtc,1382
5
+ epicsdev-2.1.1.dist-info/WHEEL,sha256=WLgqFyCfm_KASv4WHyYy0P3pM_m7J5L9k2skdKLirC8,87
6
+ epicsdev-2.1.1.dist-info/licenses/LICENSE,sha256=qj3cUKUrX4oXTb0NwuJQ44ThYDEMUfOeIjw9kkT6Qck,1072
7
+ epicsdev-2.1.1.dist-info/RECORD,,
@@ -1,7 +0,0 @@
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,,