epicsdev 1.0.0__tar.gz → 1.0.2__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-1.0.0 → epicsdev-1.0.2}/PKG-INFO +1 -1
- {epicsdev-1.0.0 → epicsdev-1.0.2}/config/epicsdev_pp.py +11 -38
- {epicsdev-1.0.0 → epicsdev-1.0.2}/epicsdev/epicsdev.py +65 -38
- {epicsdev-1.0.0 → epicsdev-1.0.2}/pyproject.toml +1 -1
- {epicsdev-1.0.0 → epicsdev-1.0.2}/LICENSE +0 -0
- {epicsdev-1.0.0 → epicsdev-1.0.2}/README.md +0 -0
- {epicsdev-1.0.0 → epicsdev-1.0.2}/config/epicsSimscope_pp.py +0 -0
- {epicsdev-1.0.0 → epicsdev-1.0.2}/epicsdev/__init__.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: epicsdev
|
|
3
|
-
Version: 1.0.
|
|
3
|
+
Version: 1.0.2
|
|
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
|
|
@@ -16,18 +16,17 @@ def slider(minValue,maxValue):
|
|
|
16
16
|
|
|
17
17
|
LargeFont = {'color':'light gray', **font(18), 'fgColor':'dark green'}
|
|
18
18
|
ButtonFont = {'font':['Open Sans Extrabold',14]}# Comic Sans MS
|
|
19
|
-
# Attributes for gray row, it should be in the first cell:
|
|
20
|
-
#GrayRow = {'ATTRIBUTES':{'color':'light gray', **font(12)}}
|
|
21
19
|
LYRow = {'ATTRIBUTES':{'color':'light yellow'}}
|
|
22
20
|
lColor = color('lightGreen')
|
|
23
21
|
|
|
24
22
|
# definition for plotting cell
|
|
25
23
|
PyPath = 'python -m'
|
|
26
|
-
PaneP2P = ' '.join([f'ch{i+1:01d}Mean' for i in range(1)])
|
|
24
|
+
PaneP2P = ' '.join([f'ch{i+1:01d}Mean ch{i+1:01d}Peak2Peak' for i in range(1)])
|
|
27
25
|
PaneWF = ' '.join([f'ch{i+1:01d}Waveform' for i in range(1)])
|
|
28
26
|
#PaneT = 'timing[1] timing[3]'
|
|
29
|
-
Plot = {'Plot':{'launch':
|
|
30
|
-
|
|
27
|
+
Plot = {'Plot':{'launch':
|
|
28
|
+
f'{PyPath} pvplot Y-5:5 -aV:epicsDev0: -#0"{PaneP2P}" -#1"{PaneWF}"',# -#2"{PaneT}"',
|
|
29
|
+
**lColor, **ButtonFont}}
|
|
31
30
|
print(f'Plot command: {Plot}')
|
|
32
31
|
#``````````````````PyPage Object``````````````````````````````````````````````
|
|
33
32
|
class PyPage():
|
|
@@ -43,7 +42,7 @@ class PyPage():
|
|
|
43
42
|
self.title = title
|
|
44
43
|
|
|
45
44
|
#``````````Page attributes, optional`````````````````````````
|
|
46
|
-
self.page = {**color(240,240,240)}
|
|
45
|
+
self.page = {**color(240,240,240)}
|
|
47
46
|
#self.page['editable'] = False
|
|
48
47
|
|
|
49
48
|
#``````````Definition of columns`````````````````````````````
|
|
@@ -74,40 +73,14 @@ string or device:parameter and the value is dictionary of the features.
|
|
|
74
73
|
#FOption = ' -file '+logreqMap.get(D,'')
|
|
75
74
|
#``````````mandatory member```````````````````````````````````````````
|
|
76
75
|
self.rows = [
|
|
77
|
-
['Device:', D, {D+'version':span(2,1)}
|
|
76
|
+
['Device:', D, {D+'version':span(2,1)},_,_,_,_],
|
|
78
77
|
['State:', D+'server','cycle:',D+'cycle',_,_,Plot], # 'Recall:', D+'setup',],
|
|
79
78
|
['Status:', {D+'status': span(8,1)}],
|
|
80
|
-
['Polling Interval:', D+'polling',
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
# #['Trigger:', D+'trigSourceS', D+'trigCouplingS', D+'trigSlopeS', 'level:', D+'trigLevelS', 'delay:', {D+'trigDelay':span(2,1)},''],
|
|
86
|
-
# ['Trigger state:',D+'trigState',' trigMode:',D+'trigMode',
|
|
87
|
-
# 'TrigLevel','TrigDelay'],
|
|
88
|
-
# [{D+'trigger':color('lightCyan')}, D+'trigSource', D+'trigCoupling',
|
|
89
|
-
# D+'trigSlope', D+'trigLevel', D+'trigDelay'],
|
|
90
|
-
[{'ATTRIBUTES':color('lightGreen')}, 'Channels:','CH1','CH2','CH3','CH4','CH5','CH6'],
|
|
91
|
-
# ['Gain:']+ChLine('VoltsPerDiv'),
|
|
92
|
-
# ['Offset:']+ChLine('Position'),
|
|
93
|
-
# ['Coupling:']+ChLine('Coupling'),
|
|
94
|
-
# ['Termination:']+ChLine('Termination'),
|
|
95
|
-
# ['On/Off:']+ChLine('OnOff'),
|
|
96
|
-
#['Delay:']+ChLine('DelayFromTriggerM'),
|
|
97
|
-
#['Waveform:']+ChLine('WaveforM'),
|
|
79
|
+
['Polling Interval:', D+'polling','nPoints:',D+'recordLength',
|
|
80
|
+
'Noise:',D+'noiseLevel',_],
|
|
81
|
+
[{'ATTRIBUTES':color('lightCyan')},
|
|
82
|
+
'Channels:','CH1','CH2','CH3','CH4','CH5','CH6'],
|
|
83
|
+
['V/div:']+ChLine('VoltsPerDiv'),
|
|
98
84
|
['Mean:']+ChLine('Mean'),
|
|
99
85
|
['Peak2Peak:']+ChLine('Peak2Peak'),
|
|
100
|
-
#[''],
|
|
101
|
-
# ["Trigger",D+'trigSourceS',D+'trigLevelS',D+'trigSlopeS',D+'trigModeS'],
|
|
102
|
-
# ['',"Setup"],
|
|
103
|
-
# ["Repair:",D+'updateDataA',D+'deviceClearA',D+'resetScopeA',D+'forceTrigA'],
|
|
104
|
-
# ["Session",D+'SaveSession',D+'RecallSession',"folder:",D+'folderS'],
|
|
105
|
-
# [D+'currentSessionS',"<-current",D+'nextSessionS',"out off",D+'sessionsM'],
|
|
106
|
-
#[{'ATTRIBUTES':{'color':'yellow'}},
|
|
107
|
-
#['tAxis:',D+'tAxis'],
|
|
108
|
-
# [LYRow,'',{'For Experts only!':{**span(6,1),**font(14)}}],
|
|
109
|
-
# [LYRow,'Scope command:', {D+'instrCmdS':span(2,1)},_,{D+'instrCmdR':span(4,1)}],
|
|
110
|
-
# [LYRow,'Special commands', {D+'instrCtrl':span(2,1)},_,_,_,_,_,],
|
|
111
|
-
# [LYRow,'Timing:',{D+'timing':span(6,1)}],
|
|
112
|
-
# [LYRow,'ActOnEvent',D+'actOnEvent','AOE_Limit',D+'aOE_Limit',_,_,_],
|
|
113
86
|
]
|
|
@@ -1,10 +1,15 @@
|
|
|
1
1
|
"""Skeleton and helper functions for creating EPICS PVAccess server"""
|
|
2
2
|
# pylint: disable=invalid-name
|
|
3
|
-
__version__= 'v1.0.
|
|
3
|
+
__version__= 'v1.0.2 26-01-18'# --list define directory to save list of PVs.
|
|
4
4
|
#TODO: NTEnums do not have structure display
|
|
5
|
+
#TODO: Add performance counters to demo.
|
|
6
|
+
#Issue: There is no way in PVAccess to specify if string PV is writable.
|
|
7
|
+
# As a workaround we append description with suffix ' Features: W' to indicate that.
|
|
5
8
|
|
|
6
9
|
import sys
|
|
7
10
|
import time
|
|
11
|
+
from time import perf_counter as timer
|
|
12
|
+
import os
|
|
8
13
|
from p4p.nt import NTScalar, NTEnum
|
|
9
14
|
from p4p.nt.enum import ntenum
|
|
10
15
|
from p4p.server import Server
|
|
@@ -104,7 +109,11 @@ def _create_PVs(pvDefs):
|
|
|
104
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"""
|
|
105
110
|
ts = time.time()
|
|
106
111
|
for defs in pvDefs:
|
|
107
|
-
|
|
112
|
+
try:
|
|
113
|
+
pname,desc,spv,extra = defs
|
|
114
|
+
except ValueError:
|
|
115
|
+
printe(f'Invalid PV definition of {defs[0]}')
|
|
116
|
+
sys.exit(1)
|
|
108
117
|
ivalue = spv.current()
|
|
109
118
|
printv(f'created pv {pname}, initial: {type(ivalue),ivalue}, extra: {extra}')
|
|
110
119
|
C_.PVs[C_.prefix+pname] = spv
|
|
@@ -202,7 +211,7 @@ def create_PVs(pvDefs=None):
|
|
|
202
211
|
U,LL,LH = 'units','limitLow','limitHigh'
|
|
203
212
|
C_.PVDefs = [
|
|
204
213
|
['version', 'Program version', SPV(__version__), {}],
|
|
205
|
-
['status', 'Server status', SPV('?','W'), {}],
|
|
214
|
+
['status', 'Server status. Features: RWE', SPV('?','W'), {}],
|
|
206
215
|
['server', 'Server control',
|
|
207
216
|
SPV('Start Stop Clear Exit Started Stopped Exited'.split(), 'WE'),
|
|
208
217
|
{'setter':set_server}],
|
|
@@ -217,76 +226,96 @@ def create_PVs(pvDefs=None):
|
|
|
217
226
|
_create_PVs(C_.PVDefs)
|
|
218
227
|
return C_.PVs
|
|
219
228
|
|
|
220
|
-
def get_externalPV(pvName, timeout=0.5):
|
|
221
|
-
"""Get value of PV from another server. That can be used to check if the
|
|
229
|
+
def get_externalPV(pvName:str, timeout=0.5):
|
|
230
|
+
"""Get value of PV from another server. That can be used to check if the
|
|
231
|
+
server is already running, or to get values from other servers."""
|
|
222
232
|
ctxt = Context('pva')
|
|
223
233
|
return ctxt.get(pvName, timeout=timeout)
|
|
224
234
|
|
|
225
|
-
def init_epicsdev(prefix, pvDefs, verbose=0):
|
|
226
|
-
"""Check if no other server is running with the same prefix
|
|
235
|
+
def init_epicsdev(prefix:str, pvDefs:list, listDir:str, verbose:str=0):
|
|
236
|
+
"""Check if no other server is running with the same prefix.
|
|
237
|
+
Create PVs and return them as a dictionary.
|
|
238
|
+
The listDir is a directory to save list of all generated PVs,
|
|
239
|
+
if no directory is given, then </tmp/pvlist/><prefix> is assumed.
|
|
240
|
+
"""
|
|
227
241
|
C_.prefix = prefix
|
|
228
242
|
C_.verbose = verbose
|
|
229
243
|
try:
|
|
230
244
|
get_externalPV(prefix+'version')
|
|
231
|
-
print(f'Server for {prefix} already running. Exiting.')
|
|
245
|
+
print(f'ERROR: Server for {prefix} already running. Exiting.')
|
|
232
246
|
sys.exit(1)
|
|
233
247
|
except TimeoutError:
|
|
234
248
|
pass
|
|
235
249
|
pvs = create_PVs(pvDefs)
|
|
250
|
+
# Save list of PVs to a file, if requested
|
|
251
|
+
if listDir != '':
|
|
252
|
+
listDir = '/tmp/pvlist/' if listDir is None else listDir
|
|
253
|
+
if not os.path.exists(listDir):
|
|
254
|
+
os.makedirs(listDir)
|
|
255
|
+
filepath = f'{listDir}{prefix[:-1]}.txt'
|
|
256
|
+
print(f'Writing list of PVs to {filepath}')
|
|
257
|
+
with open(filepath, 'w', encoding="utf-8") as f:
|
|
258
|
+
for _pvname in pvs:
|
|
259
|
+
f.write(_pvname + '\n')
|
|
236
260
|
return pvs
|
|
237
261
|
|
|
238
|
-
#``````````````````
|
|
262
|
+
#``````````````````Demo````````````````````````````````````````````````````````
|
|
239
263
|
if __name__ == "__main__":
|
|
264
|
+
print(f'epicsdev multiadc demo server {__version__}')
|
|
240
265
|
import numpy as np
|
|
241
266
|
import argparse
|
|
242
267
|
|
|
243
268
|
def myPVDefs():
|
|
244
269
|
"""Example of PV definitions"""
|
|
245
270
|
SET,U,LL,LH = 'setter','units','limitLow','limitHigh'
|
|
246
|
-
alarm = {'valueAlarm':{'lowAlarmLimit'
|
|
271
|
+
alarm = {'valueAlarm':{'lowAlarmLimit':-9., 'highAlarmLimit':9.}}
|
|
247
272
|
return [ # device-specific PVs
|
|
248
|
-
['noiseLevel', 'Noise amplitude', SPV(1.E-6,'W'), {SET:set_noise}],
|
|
273
|
+
['noiseLevel', 'Noise amplitude', SPV(1.E-6,'W'), {SET:set_noise, U:'V'}],
|
|
249
274
|
['tAxis', 'Full scale of horizontal axis', SPV([0.]), {U:'S'}],
|
|
250
275
|
['recordLength','Max number of points', SPV(100,'W','u32'),
|
|
251
276
|
{LL:4,LH:1000000, SET:set_recordLength}],
|
|
252
277
|
['ch1Offset', 'Offset', SPV(0.,'W'), {U:'du'}],
|
|
253
278
|
['ch1VoltsPerDiv', 'Vertical scale', SPV(1E-3,'W'), {U:'V/du'}],
|
|
254
|
-
['
|
|
255
|
-
['
|
|
256
|
-
['
|
|
257
|
-
['
|
|
258
|
-
['alarm', 'PV with alarm', SPV(0,'WA'), alarm],
|
|
279
|
+
['ch1Waveform', 'Waveform array', SPV([0.]), {U:'du'}],
|
|
280
|
+
['ch1Mean', 'Mean of the waveform', SPV(0.,'A'), {U:'du'}],
|
|
281
|
+
['ch1Peak2Peak','Peak-to-peak amplitude', SPV(0.,'A'), {U:'du',**alarm}],
|
|
282
|
+
['alarm', 'PV with alarm', SPV(0,'WA'), {U:'du',**alarm}],
|
|
259
283
|
]
|
|
260
|
-
nPatterns = 100 # number of patterns
|
|
284
|
+
nPatterns = 100 # number of waveform patterns.
|
|
261
285
|
pargs = None
|
|
262
|
-
|
|
286
|
+
rng = np.random.default_rng(nPatterns)
|
|
287
|
+
nPoints = 100
|
|
263
288
|
|
|
264
289
|
def set_recordLength(value):
|
|
265
290
|
"""Record length have changed. The tAxis should be updated accordingly."""
|
|
266
291
|
printi(f'Setting tAxis to {value}')
|
|
267
|
-
publish('tAxis', np.arange(value)*
|
|
292
|
+
publish('tAxis', np.arange(value)*1.E-6)
|
|
268
293
|
publish('recordLength', value)
|
|
294
|
+
set_noise(pvv('noiseLevel')) # Re-initialize noise array, because its size depends on recordLength
|
|
269
295
|
|
|
270
296
|
def set_noise(level):
|
|
271
297
|
"""Noise level have changed. Update noise array."""
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
298
|
+
v = float(level)
|
|
299
|
+
recordLength = pvv('recordLength')
|
|
300
|
+
ts = timer()
|
|
301
|
+
pargs.noise = np.random.normal(scale=0.5*level, size=recordLength+nPatterns)# 45ms/1e6 points
|
|
302
|
+
printi(f'Noise array[{len(pargs.noise)}] updated with level {v:.4g} V. in {timer()-ts:.4g} S.')
|
|
275
303
|
publish('noiseLevel', level)
|
|
276
|
-
|
|
304
|
+
|
|
277
305
|
def init(recordLength):
|
|
278
306
|
"""Testing function. Do not use in production code."""
|
|
279
307
|
set_recordLength(recordLength)
|
|
280
|
-
set_noise(pvv('noiseLevel'))
|
|
308
|
+
#set_noise(pvv('noiseLevel')) # already called from set_recordLength
|
|
281
309
|
|
|
282
310
|
def poll():
|
|
283
311
|
"""Example of polling function"""
|
|
284
|
-
pattern = C_.cycle % nPatterns
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
312
|
+
#pattern = C_.cycle % nPatterns# produces sliding
|
|
313
|
+
pattern = rng.integers(0, nPatterns)
|
|
314
|
+
cycle = pvv('cycle')
|
|
315
|
+
printv(f'cycle {repr(cycle)}')
|
|
316
|
+
publish('cycle', cycle + 1)
|
|
288
317
|
wf = pargs.noise[pattern:pattern+pvv('recordLength')].copy()
|
|
289
|
-
wf
|
|
318
|
+
wf /= pvv('ch1VoltsPerDiv')
|
|
290
319
|
wf += pvv('ch1Offset')
|
|
291
320
|
publish('ch1Waveform', wf)
|
|
292
321
|
publish('ch1Peak2Peak', np.ptp(wf))
|
|
@@ -296,22 +325,20 @@ if __name__ == "__main__":
|
|
|
296
325
|
parser = argparse.ArgumentParser(description = __doc__,
|
|
297
326
|
formatter_class=argparse.ArgumentDefaultsHelpFormatter,
|
|
298
327
|
epilog=f'{__version__}')
|
|
299
|
-
parser.add_argument('-l', '--
|
|
300
|
-
'
|
|
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.')
|
|
301
330
|
parser.add_argument('-p', '--prefix', default='epicsDev0:', help=
|
|
302
331
|
'Prefix to be prepended to all PVs')
|
|
303
|
-
|
|
332
|
+
# The rest of options are not essential, they can be controlled at runtime using PVs.
|
|
333
|
+
parser.add_argument('-n', '--npoints', type=int, default=nPoints, help=
|
|
304
334
|
'Number of points in the waveform')
|
|
305
335
|
parser.add_argument('-v', '--verbose', action='count', default=0, help=
|
|
306
|
-
'Show more log messages (-vv: show even more)')
|
|
336
|
+
'Show more log messages (-vv: show even more)')
|
|
307
337
|
pargs = parser.parse_args()
|
|
338
|
+
print(pargs)
|
|
308
339
|
|
|
309
340
|
# Initialize epicsdev and PVs
|
|
310
|
-
PVs = init_epicsdev(pargs.prefix, myPVDefs(), pargs.verbose)
|
|
311
|
-
if pargs.listPVs:
|
|
312
|
-
print('List of PVs:')
|
|
313
|
-
for _pvname in PVs:
|
|
314
|
-
print(_pvname)
|
|
341
|
+
PVs = init_epicsdev(pargs.prefix, myPVDefs(), pargs.list, pargs.verbose)
|
|
315
342
|
|
|
316
343
|
# Initialize the device, using pargs if needed. That can be used to set the number of points in the waveform, for example.
|
|
317
344
|
init(pargs.npoints)
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|