epicsdev 2.0.1__tar.gz → 2.1.0__tar.gz
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-2.0.1 → epicsdev-2.1.0}/PKG-INFO +1 -1
- epicsdev-2.0.1/config/multiadc_pp.py → epicsdev-2.1.0/config/epicsdev_pp.py +29 -23
- epicsdev-2.1.0/config/multiadc1_pp.py +6 -0
- epicsdev-2.0.1/config/epicsdev_pp.py → epicsdev-2.1.0/config/multiadc_pp.py +32 -24
- {epicsdev-2.0.1 → epicsdev-2.1.0}/epicsdev/epicsdev.py +56 -27
- {epicsdev-2.0.1 → epicsdev-2.1.0}/epicsdev/multiadc.py +10 -14
- {epicsdev-2.0.1 → epicsdev-2.1.0}/pyproject.toml +1 -1
- {epicsdev-2.0.1 → epicsdev-2.1.0}/.github/copilot-instructions.md +0 -0
- {epicsdev-2.0.1 → epicsdev-2.1.0}/LICENSE +0 -0
- {epicsdev-2.0.1 → epicsdev-2.1.0}/README.md +0 -0
- {epicsdev-2.0.1 → epicsdev-2.1.0}/config/epicsSimscope_pp.py +0 -0
- {epicsdev-2.0.1 → epicsdev-2.1.0}/docs/epicsdev_pvplot.jpg +0 -0
- {epicsdev-2.0.1 → epicsdev-2.1.0}/docs/epicsdev_pypet.png +0 -0
- {epicsdev-2.0.1 → epicsdev-2.1.0}/epicsdev/__init__.py +0 -0
- {epicsdev-2.0.1 → epicsdev-2.1.0}/fallback/multiadc.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: epicsdev
|
|
3
|
-
Version: 2.0
|
|
3
|
+
Version: 2.1.0
|
|
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
|
|
@@ -1,7 +1,10 @@
|
|
|
1
|
-
"""Pypet page for epicdev.
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
1
|
+
"""Pypet page for epicdev.epicsdev.
|
|
2
|
+
Device instance is configured via environment variable EPICSDEV,
|
|
3
|
+
default instance is epicsdev0:."""
|
|
4
|
+
# pylint: disable=invalid-name
|
|
5
|
+
__version__ = 'v0.1.0 2026-01-31'#
|
|
6
|
+
|
|
7
|
+
import os
|
|
5
8
|
|
|
6
9
|
#``````````````````Definitions````````````````````````````````````````````````
|
|
7
10
|
# python expressions and functions, used in the spreadsheet
|
|
@@ -18,28 +21,27 @@ LargeFont = {'color':'light gray', **font(18), 'fgColor':'dark green'}
|
|
|
18
21
|
ButtonFont = {'font':['Open Sans Extrabold',14]}# Comic Sans MS
|
|
19
22
|
LYRow = {'ATTRIBUTES':{'color':'light yellow'}}
|
|
20
23
|
lColor = color('lightGreen')
|
|
21
|
-
|
|
22
|
-
# definition for plotting cell
|
|
23
24
|
PyPath = 'python -m'
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
print(f'Plot command: {Plot}')
|
|
25
|
+
|
|
26
|
+
try:
|
|
27
|
+
Instance = f"epicsDev{os.environ['EPICSDEV']}:"
|
|
28
|
+
except KeyError:
|
|
29
|
+
Instance = 'epicsDev0:'
|
|
30
|
+
|
|
31
31
|
#``````````````````PyPage Object``````````````````````````````````````````````
|
|
32
32
|
class PyPage():
|
|
33
|
-
def __init__(self, instance=
|
|
34
|
-
title="
|
|
33
|
+
def __init__(self, instance=None,
|
|
34
|
+
title="epicsdev", channels=1):
|
|
35
35
|
"""instance: unique name of the page.
|
|
36
36
|
For EPICS it is usually device prefix
|
|
37
37
|
"""
|
|
38
|
+
if instance is None:
|
|
39
|
+
instance = Instance
|
|
38
40
|
print(f'Instantiating Page {instance,title} with {channels} channels')
|
|
39
41
|
|
|
40
42
|
#``````````Mandatory class members starts here````````````````````````
|
|
41
43
|
self.namespace = 'PVA'
|
|
42
|
-
self.title =
|
|
44
|
+
self.title = instance[:-1]
|
|
43
45
|
|
|
44
46
|
#``````````Page attributes, optional`````````````````````````
|
|
45
47
|
self.page = {**color(240,240,240)}
|
|
@@ -70,18 +72,22 @@ string or device:parameter and the value is dictionary of the features.
|
|
|
70
72
|
#``````````Abbreviations, used in cell definitions
|
|
71
73
|
def ChLine(suffix):
|
|
72
74
|
return [f'{D}c{ch+1:02d}{suffix}' for ch in range(channels)]
|
|
73
|
-
|
|
75
|
+
PaneP2P = ' '.join([f'c{i+1:02d}Mean c{i+1:02d}Peak2Peak' for i in range(channels)])
|
|
76
|
+
PaneWF = ' '.join([f'c{i+1:02d}Waveform' for i in range(channels)])
|
|
77
|
+
#PaneT = 'timing[1] timing[3]'
|
|
78
|
+
Plot = {'Plot':{'launch':
|
|
79
|
+
f'{PyPath} pvplot Y-5:5 -aV:{instance} -#0"{PaneP2P}" -#1"{PaneWF}"',# -#2"{PaneT}"',
|
|
80
|
+
**lColor, **ButtonFont}}
|
|
81
|
+
print(f'Plot command: {Plot}')
|
|
74
82
|
#``````````mandatory member```````````````````````````````````````````
|
|
75
83
|
self.rows = [
|
|
76
|
-
['Device:', D,
|
|
77
|
-
['State:', D+'server','cycle:', D+'cycle',_,_,Plot], # 'Recall:', D+'setup',],
|
|
84
|
+
['Device:',D, D+'server', D+'version', 'host:',D+'host',_],
|
|
78
85
|
['Status:', {D+'status': span(8,1)}],
|
|
79
|
-
['
|
|
80
|
-
|
|
86
|
+
['Cycle time:',D+'cycleTime', 'Sleep:',D+'sleep', 'Cycle:',D+'cycle', Plot],
|
|
87
|
+
['nPoints:',D+'recordLength','Noise:',D+'noiseLevel',_,_,_],
|
|
81
88
|
[{'ATTRIBUTES':{**color('lightCyan'),**just(1)}},
|
|
82
|
-
'Channels:','CH1','CH2','CH3','CH4','CH5','CH6'],
|
|
89
|
+
'Channels:', 'CH1', 'CH2', 'CH3', 'CH4', 'CH5', 'CH6'],
|
|
83
90
|
['V/div:']+ChLine('VoltsPerDiv'),
|
|
84
91
|
['Mean:']+ChLine('Mean'),
|
|
85
92
|
['Peak2Peak:']+ChLine('Peak2Peak'),
|
|
86
|
-
#['Waveform:']+ChLine('Waveform'),
|
|
87
93
|
]
|
|
@@ -1,7 +1,10 @@
|
|
|
1
|
-
"""Pypet page for epicdev.
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
1
|
+
"""Pypet page for epicdev.multiadc, simulated multi-channel ADC device server.
|
|
2
|
+
Device instance is configured via environment variable MULTIADC,
|
|
3
|
+
default instance is multiadc0:."""
|
|
4
|
+
# pylint: disable=invalid-name
|
|
5
|
+
__version__ = 'v0.1.0 2026-01-31'#
|
|
6
|
+
|
|
7
|
+
import os
|
|
5
8
|
|
|
6
9
|
#``````````````````Definitions````````````````````````````````````````````````
|
|
7
10
|
# python expressions and functions, used in the spreadsheet
|
|
@@ -18,28 +21,27 @@ LargeFont = {'color':'light gray', **font(18), 'fgColor':'dark green'}
|
|
|
18
21
|
ButtonFont = {'font':['Open Sans Extrabold',14]}# Comic Sans MS
|
|
19
22
|
LYRow = {'ATTRIBUTES':{'color':'light yellow'}}
|
|
20
23
|
lColor = color('lightGreen')
|
|
21
|
-
|
|
22
|
-
# definition for plotting cell
|
|
23
24
|
PyPath = 'python -m'
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
#``````````````````PyPage Object``````````````````````````````````````````````
|
|
25
|
+
|
|
26
|
+
try:
|
|
27
|
+
Instance = f"multiadc{os.environ['EPICSDEV_MULTIADC']}:"
|
|
28
|
+
except KeyError:
|
|
29
|
+
Instance = 'multiadc0:'
|
|
30
|
+
|
|
31
|
+
#``````````````````Mandatory object PyPage````````````````````````````````````
|
|
32
32
|
class PyPage():
|
|
33
|
-
def __init__(self, instance=
|
|
34
|
-
title=
|
|
33
|
+
def __init__(self, instance=None,
|
|
34
|
+
title=None, channels=6):
|
|
35
35
|
"""instance: unique name of the page.
|
|
36
36
|
For EPICS it is usually device prefix
|
|
37
37
|
"""
|
|
38
|
+
if instance is None:
|
|
39
|
+
instance = Instance
|
|
38
40
|
print(f'Instantiating Page {instance,title} with {channels} channels')
|
|
39
41
|
|
|
40
42
|
#``````````Mandatory class members starts here````````````````````````
|
|
41
43
|
self.namespace = 'PVA'
|
|
42
|
-
self.title = title
|
|
44
|
+
self.title = title if title is not None else instance[:-1]
|
|
43
45
|
|
|
44
46
|
#``````````Page attributes, optional`````````````````````````
|
|
45
47
|
self.page = {**color(240,240,240)}
|
|
@@ -69,18 +71,24 @@ string or device:parameter and the value is dictionary of the features.
|
|
|
69
71
|
|
|
70
72
|
#``````````Abbreviations, used in cell definitions
|
|
71
73
|
def ChLine(suffix):
|
|
72
|
-
return [f'{D}
|
|
73
|
-
|
|
74
|
+
return [f'{D}c{ch+1:02d}{suffix}' for ch in range(channels)]
|
|
75
|
+
PaneP2P = ' '.join([f'c{i+1:02d}Mean c{i+1:02d}Peak2Peak' for i in range(channels)])
|
|
76
|
+
PaneWF = ' '.join([f'c{i+1:02d}Waveform' for i in range(channels)])
|
|
77
|
+
#PaneT = 'timing[1] timing[3]'
|
|
78
|
+
Plot = {'Plot':{'launch':
|
|
79
|
+
f'{PyPath} pvplot Y-5:5 -aV:{instance} -#0"{PaneP2P}" -#1"{PaneWF}"',# -#2"{PaneT}"',
|
|
80
|
+
**lColor, **ButtonFont}}
|
|
81
|
+
print(f'Plot command: {Plot}')
|
|
74
82
|
#``````````mandatory member```````````````````````````````````````````
|
|
75
83
|
self.rows = [
|
|
76
|
-
['Device:', D,
|
|
77
|
-
['State:', D+'server', 'cycle:', D+'cycle',_,_, Plot], # 'Recall:', D+'setup',],
|
|
84
|
+
['Device:',D, D+'server', D+'version', 'host:',D+'host',_],
|
|
78
85
|
['Status:', {D+'status': span(8,1)}],
|
|
79
|
-
['
|
|
80
|
-
|
|
86
|
+
['Cycle time:',D+'cycleTime', 'Sleep:',D+'sleep', 'Cycle:',D+'cycle', Plot],
|
|
87
|
+
['nPoints:',D+'recordLength','Noise:',D+'noiseLevel', 'Channels:',D+'channels',_],
|
|
81
88
|
[{'ATTRIBUTES':{**color('lightCyan'),**just(1)}},
|
|
82
|
-
|
|
89
|
+
'Channels:','CH1','CH2','CH3','CH4','CH5','CH6'],
|
|
83
90
|
['V/div:']+ChLine('VoltsPerDiv'),
|
|
84
91
|
['Mean:']+ChLine('Mean'),
|
|
85
92
|
['Peak2Peak:']+ChLine('Peak2Peak'),
|
|
93
|
+
#['Waveform:']+ChLine('Waveform'),
|
|
86
94
|
]
|
|
@@ -1,12 +1,13 @@
|
|
|
1
1
|
"""Skeleton and helper functions for creating EPICS PVAccess server"""
|
|
2
2
|
# pylint: disable=invalid-name
|
|
3
|
-
__version__= 'v2.0
|
|
3
|
+
__version__= 'v2.1.0 26-01-31'# polling renamed to sleep. Sleep function added.
|
|
4
4
|
#TODO add mandatory PV: host, to identify the server host.
|
|
5
5
|
#Issue: There is no way in PVAccess to specify if string PV is writable.
|
|
6
6
|
# As a workaround we append description with suffix ' Features: W' to indicate that.
|
|
7
7
|
|
|
8
8
|
import sys
|
|
9
|
-
|
|
9
|
+
import time
|
|
10
|
+
from time import perf_counter as timer
|
|
10
11
|
import os
|
|
11
12
|
from socket import gethostname
|
|
12
13
|
from p4p.nt import NTScalar, NTEnum
|
|
@@ -15,6 +16,8 @@ from p4p.server import Server
|
|
|
15
16
|
from p4p.server.thread import SharedPV
|
|
16
17
|
from p4p.client.thread import Context
|
|
17
18
|
|
|
19
|
+
PeriodicUpdateInterval = 10. # seconds
|
|
20
|
+
|
|
18
21
|
#``````````````````Module Storage`````````````````````````````````````````````
|
|
19
22
|
def _serverStateChanged(newState:str):
|
|
20
23
|
"""Dummy serverStateChanged function"""
|
|
@@ -28,6 +31,10 @@ class C_():
|
|
|
28
31
|
PVs = {}
|
|
29
32
|
PVDefs = []
|
|
30
33
|
serverStateChanged = _serverStateChanged
|
|
34
|
+
lastCycleTime = time.time()
|
|
35
|
+
lastUpdateTime = 0.
|
|
36
|
+
cycleTimeSum = 0.
|
|
37
|
+
cyclesAfterUpdate = 0
|
|
31
38
|
|
|
32
39
|
#```````````````````Helper methods````````````````````````````````````````````
|
|
33
40
|
def serverState():
|
|
@@ -35,7 +42,7 @@ def serverState():
|
|
|
35
42
|
cached in C_ to avoid unnecessary get() calls."""
|
|
36
43
|
return C_.serverState
|
|
37
44
|
def _printTime():
|
|
38
|
-
return strftime("%m%d:%H%M%S")
|
|
45
|
+
return time.strftime("%m%d:%H%M%S")
|
|
39
46
|
def printi(msg):
|
|
40
47
|
"""Print info message and publish it to status PV."""
|
|
41
48
|
print(f'inf_@{_printTime()}: {msg}')
|
|
@@ -81,7 +88,7 @@ def publish(pvName:str, value, ifChanged=False, t=None):
|
|
|
81
88
|
print(f'WARNING: PV {pvName} not found. Cannot publish value.')
|
|
82
89
|
return
|
|
83
90
|
if t is None:
|
|
84
|
-
t = time()
|
|
91
|
+
t = time.time()
|
|
85
92
|
if not ifChanged or pv.current() != value:
|
|
86
93
|
pv.post(value, timestamp=t)
|
|
87
94
|
|
|
@@ -127,7 +134,7 @@ def SPV(initial, meta='', vtype=None):
|
|
|
127
134
|
|
|
128
135
|
#``````````````````create_PVs()```````````````````````````````````````````````
|
|
129
136
|
def _create_PVs(pvDefs):
|
|
130
|
-
ts = time()
|
|
137
|
+
ts = time.time()
|
|
131
138
|
for defs in pvDefs:
|
|
132
139
|
try:
|
|
133
140
|
pname,desc,spv,extra = defs
|
|
@@ -174,7 +181,7 @@ def _create_PVs(pvDefs):
|
|
|
174
181
|
if spv.writable:
|
|
175
182
|
@spv.put
|
|
176
183
|
def handle(spv, op):
|
|
177
|
-
ct = time()
|
|
184
|
+
ct = time.time()
|
|
178
185
|
vv = op.value()
|
|
179
186
|
vr = vv.raw.value
|
|
180
187
|
current = spv._wrap(spv.current())
|
|
@@ -259,8 +266,12 @@ def create_PVs(pvDefs=None):
|
|
|
259
266
|
{'setter':set_server}],
|
|
260
267
|
['verbose', 'Debugging verbosity', SPV(C_.verbose,'W','u8'),
|
|
261
268
|
{'setter':set_verbose, LL:0,LH:3}],
|
|
262
|
-
['
|
|
263
|
-
|
|
269
|
+
['sleep', 'Pause in the main loop, it could be useful for throttling the data output',
|
|
270
|
+
SPV(1.0,'W'), {U:'S', LL:0.001, LH:10.1}],
|
|
271
|
+
['cycle', 'Cycle number, published every {PeriodicUpdateInterval} S.',
|
|
272
|
+
SPV(0,'','u32'), {}],
|
|
273
|
+
['cycleTime','Average cycle time including sleep, published every {PeriodicUpdateInterval} S',
|
|
274
|
+
SPV(0.), {U:'S'}],
|
|
264
275
|
]
|
|
265
276
|
# append application's PVs, defined in the pvDefs and create map of
|
|
266
277
|
# providers
|
|
@@ -301,7 +312,8 @@ def init_epicsdev(prefix:str, pvDefs:list, verbose=0,
|
|
|
301
312
|
host = repr(get_externalPV(prefix+'host')).replace("'",'')
|
|
302
313
|
print(f'ERROR: Server for {prefix} already running at {host}. Exiting.')
|
|
303
314
|
sys.exit(1)
|
|
304
|
-
except TimeoutError:
|
|
315
|
+
except TimeoutError:
|
|
316
|
+
pass
|
|
305
317
|
|
|
306
318
|
# No existing server found. Creating PVs.
|
|
307
319
|
pvs = create_PVs(pvDefs)
|
|
@@ -315,8 +327,28 @@ def init_epicsdev(prefix:str, pvDefs:list, verbose=0,
|
|
|
315
327
|
with open(filepath, 'w', encoding="utf-8") as f:
|
|
316
328
|
for _pvname in pvs:
|
|
317
329
|
f.write(_pvname + '\n')
|
|
330
|
+
printi(f'Hosting {len(pvs)} PVs')
|
|
318
331
|
return pvs
|
|
319
332
|
|
|
333
|
+
def sleep():
|
|
334
|
+
"""Sleep function to be called in the main loop. It updates cycleTime PV
|
|
335
|
+
and sleeps for the time specified in sleep PV."""
|
|
336
|
+
tnow = time.time()
|
|
337
|
+
C_.cycleTimeSum += tnow - C_.lastCycleTime
|
|
338
|
+
C_.lastCycleTime = tnow
|
|
339
|
+
C_.cyclesAfterUpdate += 1
|
|
340
|
+
C_.cycle += 1
|
|
341
|
+
printv(f'cycle {C_.cycle}')
|
|
342
|
+
if tnow - C_.lastUpdateTime > PeriodicUpdateInterval:
|
|
343
|
+
avgCycleTime = C_.cycleTimeSum / C_.cyclesAfterUpdate
|
|
344
|
+
printv(f'Average cycle time: {avgCycleTime:.6f} S.')
|
|
345
|
+
publish('cycle', C_.cycle)
|
|
346
|
+
publish('cycleTime', avgCycleTime)
|
|
347
|
+
C_.lastUpdateTime = tnow
|
|
348
|
+
C_.cycleTimeSum = 0.
|
|
349
|
+
C_.cyclesAfterUpdate = 0
|
|
350
|
+
time.sleep(pvv('sleep'))
|
|
351
|
+
|
|
320
352
|
#``````````````````Demo````````````````````````````````````````````````````````
|
|
321
353
|
if __name__ == "__main__":
|
|
322
354
|
import numpy as np
|
|
@@ -331,11 +363,11 @@ if __name__ == "__main__":
|
|
|
331
363
|
['tAxis', 'Full scale of horizontal axis', SPV([0.]), {U:'S'}],
|
|
332
364
|
['recordLength','Max number of points', SPV(100,'W','u32'),
|
|
333
365
|
{LL:4,LH:1000000, SET:set_recordLength}],
|
|
334
|
-
['
|
|
335
|
-
['
|
|
336
|
-
['
|
|
337
|
-
['
|
|
338
|
-
['
|
|
366
|
+
['c01Offset', 'Offset', SPV(0.,'W'), {U:'du'}],
|
|
367
|
+
['c01VoltsPerDiv', 'Vertical scale', SPV(1E-3,'W'), {U:'V/du'}],
|
|
368
|
+
['c01Waveform', 'Waveform array', SPV([0.]), {U:'du'}],
|
|
369
|
+
['c01Mean', 'Mean of the waveform', SPV(0.,'A'), {U:'du'}],
|
|
370
|
+
['c01Peak2Peak','Peak-to-peak amplitude', SPV(0.,'A'), {U:'du',**alarm}],
|
|
339
371
|
['alarm', 'PV with alarm', SPV(0,'WA'), {U:'du',**alarm}],
|
|
340
372
|
]
|
|
341
373
|
nPatterns = 100 # number of waveform patterns.
|
|
@@ -363,23 +395,20 @@ if __name__ == "__main__":
|
|
|
363
395
|
publish('noiseLevel', level)
|
|
364
396
|
|
|
365
397
|
def init(recordLength):
|
|
366
|
-
"""
|
|
398
|
+
"""Example of device initialization function"""
|
|
367
399
|
set_recordLength(recordLength)
|
|
368
400
|
#set_noise(pvv('noiseLevel')) # already called from set_recordLength
|
|
369
401
|
|
|
370
402
|
def poll():
|
|
371
|
-
"""Example of polling function"""
|
|
403
|
+
"""Example of polling function. Called every cycle when server is running."""
|
|
372
404
|
#pattern = C_.cycle % nPatterns# produces sliding
|
|
373
405
|
pattern = rng.integers(0, nPatterns)
|
|
374
|
-
cycle = pvv('cycle')
|
|
375
|
-
printv(f'cycle {repr(cycle)}')
|
|
376
|
-
publish('cycle', cycle + 1)
|
|
377
406
|
wf = pargs.noise[pattern:pattern+pvv('recordLength')].copy()
|
|
378
|
-
wf /= pvv('
|
|
379
|
-
wf += pvv('
|
|
380
|
-
publish('
|
|
381
|
-
publish('
|
|
382
|
-
publish('
|
|
407
|
+
wf /= pvv('c01VoltsPerDiv')
|
|
408
|
+
wf += pvv('c01Offset')
|
|
409
|
+
publish('c01Waveform', wf)
|
|
410
|
+
publish('c01Peak2Peak', np.ptp(wf))
|
|
411
|
+
publish('c01Mean', np.mean(wf))
|
|
383
412
|
|
|
384
413
|
# Argument parsing
|
|
385
414
|
parser = argparse.ArgumentParser(description = __doc__,
|
|
@@ -389,7 +418,7 @@ if __name__ == "__main__":
|
|
|
389
418
|
'Device name, the PV name will be <device><index>:')
|
|
390
419
|
parser.add_argument('-i', '--index', default='0', help=
|
|
391
420
|
'Device index, the PV name will be <device><index>:')
|
|
392
|
-
parser.add_argument('-l', '--list',
|
|
421
|
+
parser.add_argument('-l', '--list', nargs='?', help=(
|
|
393
422
|
'Directory to save list of all generated PVs, if no directory is given, '
|
|
394
423
|
'then </tmp/pvlist/><prefix> is assumed.'))
|
|
395
424
|
# The rest of options are not essential, they can be controlled at runtime using PVs.
|
|
@@ -413,12 +442,12 @@ if __name__ == "__main__":
|
|
|
413
442
|
|
|
414
443
|
# Main loop
|
|
415
444
|
server = Server(providers=[PVs])
|
|
416
|
-
printi(f'Server started
|
|
445
|
+
printi(f'Server started. Sleeping per cycle: {repr(pvv("sleep"))} S.')
|
|
417
446
|
while True:
|
|
418
447
|
state = serverState()
|
|
419
448
|
if state.startswith('Exit'):
|
|
420
449
|
break
|
|
421
450
|
if not state.startswith('Stop'):
|
|
422
451
|
poll()
|
|
423
|
-
sleep(
|
|
452
|
+
sleep()
|
|
424
453
|
printi('Server is exited')
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
"""Simulated multi-channel ADC device server using epicsdev module."""
|
|
2
2
|
# pylint: disable=invalid-name
|
|
3
|
-
__version__= '
|
|
3
|
+
__version__= 'v2.1.0 26-01-31'# updated for epicsdev v2.1.0
|
|
4
4
|
|
|
5
5
|
import sys
|
|
6
6
|
import time
|
|
@@ -9,7 +9,7 @@ import numpy as np
|
|
|
9
9
|
import argparse
|
|
10
10
|
|
|
11
11
|
from .epicsdev import Server, Context, init_epicsdev, serverState, publish
|
|
12
|
-
from .epicsdev import pvv, printi, printv, SPV, set_server
|
|
12
|
+
from .epicsdev import pvv, printi, printv, SPV, set_server, sleep
|
|
13
13
|
|
|
14
14
|
|
|
15
15
|
def myPVDefs():
|
|
@@ -17,6 +17,7 @@ def myPVDefs():
|
|
|
17
17
|
SET,U,LL,LH = 'setter','units','limitLow','limitHigh'
|
|
18
18
|
alarm = {'valueAlarm':{'lowAlarmLimit':-9., 'highAlarmLimit':9.}}
|
|
19
19
|
pvDefs = [ # device-specific PVs
|
|
20
|
+
['channels', 'Number of device channels', SPV(pargs.channels), {}],
|
|
20
21
|
['externalControl', 'Name of external PV, which controls the server',
|
|
21
22
|
SPV('Start Stop Clear Exit Started Stopped Exited'.split(), 'WD'), {}],
|
|
22
23
|
['noiseLevel', 'Noise amplitude', SPV(1.E-4,'W'), {SET:set_noise, U:'V'}],
|
|
@@ -48,7 +49,7 @@ nPatterns = 100 # number of waveform patterns.
|
|
|
48
49
|
rng = np.random.default_rng(nPatterns)
|
|
49
50
|
|
|
50
51
|
#``````````````````Setter functions for PVs```````````````````````````````````
|
|
51
|
-
def set_recordLength(value):
|
|
52
|
+
def set_recordLength(value, *_):
|
|
52
53
|
"""Record length have changed. The tAxis should be updated accordingly."""
|
|
53
54
|
printi(f'Setting tAxis to {value}')
|
|
54
55
|
publish('tAxis', np.arange(value)*1.E-6)
|
|
@@ -56,7 +57,7 @@ def set_recordLength(value):
|
|
|
56
57
|
# Re-initialize noise array, because its size depends on recordLength
|
|
57
58
|
set_noise(pvv('noiseLevel'))
|
|
58
59
|
|
|
59
|
-
def set_noise(level):
|
|
60
|
+
def set_noise(level, *_):
|
|
60
61
|
"""Noise level have changed. Update noise array."""
|
|
61
62
|
v = float(level)
|
|
62
63
|
recordLength = pvv('recordLength')
|
|
@@ -66,7 +67,7 @@ def set_noise(level):
|
|
|
66
67
|
printi(f'Noise array[{len(pargs.noise)}] updated with level {v:.4g} V. in {timer()-ts:.4g} S.')
|
|
67
68
|
publish('noiseLevel', level)
|
|
68
69
|
|
|
69
|
-
def set_externalControl(value):
|
|
70
|
+
def set_externalControl(value, *_):
|
|
70
71
|
"""External control PV have changed. Control the server accordingly."""
|
|
71
72
|
pvname = str(value)
|
|
72
73
|
if pvname in (None,'0'):
|
|
@@ -91,16 +92,12 @@ def serverStateChanged(newState:str):
|
|
|
91
92
|
publish('cycle', 0)
|
|
92
93
|
|
|
93
94
|
def init(recordLength):
|
|
94
|
-
"""
|
|
95
|
+
"""Device initialization function"""
|
|
95
96
|
set_recordLength(recordLength)
|
|
96
97
|
#set_externalControl(pargs.prefix + pargs.external)
|
|
97
98
|
|
|
98
99
|
def poll():
|
|
99
|
-
"""
|
|
100
|
-
#pattern = C_.cycle % nPatterns# produces sliding
|
|
101
|
-
cycle = pvv('cycle')
|
|
102
|
-
printv(f'cycle {repr(cycle)}')
|
|
103
|
-
publish('cycle', cycle + 1)
|
|
100
|
+
"""Device polling function, called every cycle when server is running"""
|
|
104
101
|
for ch in range(pargs.channels):
|
|
105
102
|
pattern = rng.integers(0, nPatterns)
|
|
106
103
|
chstr = f'c{ch+1:02}'
|
|
@@ -143,7 +140,6 @@ PVs = init_epicsdev(pargs.prefix, myPVDefs(), pargs.verbose,
|
|
|
143
140
|
# print('List of PVs:')
|
|
144
141
|
# for _pvname in PVs:
|
|
145
142
|
# print(_pvname)
|
|
146
|
-
printi(f'Hosting {len(PVs)} PVs')
|
|
147
143
|
|
|
148
144
|
# Initialize the device, using pargs if needed.
|
|
149
145
|
# That can be used to set the number of points in the waveform, for example.
|
|
@@ -154,12 +150,12 @@ set_server('Start')
|
|
|
154
150
|
|
|
155
151
|
#``````````````````Main loop``````````````````````````````````````````````````
|
|
156
152
|
server = Server(providers=[PVs])
|
|
157
|
-
printi(f'Server started
|
|
153
|
+
printi(f'Server started. Sleeping per cycle: {repr(pvv("sleep"))} S.')
|
|
158
154
|
while True:
|
|
159
155
|
state = serverState()
|
|
160
156
|
if state.startswith('Exit'):
|
|
161
157
|
break
|
|
162
158
|
if not state.startswith('Stop'):
|
|
163
159
|
poll()
|
|
164
|
-
|
|
160
|
+
sleep()
|
|
165
161
|
printi('Server is exited')
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|