epicsdev 1.0.1__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.1 → epicsdev-1.0.2}/PKG-INFO +1 -1
- {epicsdev-1.0.1 → epicsdev-1.0.2}/config/epicsdev_pp.py +11 -40
- {epicsdev-1.0.1 → epicsdev-1.0.2}/epicsdev/epicsdev.py +56 -34
- {epicsdev-1.0.1 → epicsdev-1.0.2}/pyproject.toml +1 -1
- {epicsdev-1.0.1 → epicsdev-1.0.2}/LICENSE +0 -0
- {epicsdev-1.0.1 → epicsdev-1.0.2}/README.md +0 -0
- {epicsdev-1.0.1 → epicsdev-1.0.2}/config/epicsSimscope_pp.py +0 -0
- {epicsdev-1.0.1 → 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,42 +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
|
-
# ['Horizontal scale:', D+'timePerDiv', ' samples:', D+'recLength',
|
|
86
|
-
# 'SamplRate:', {D+'samplingRate':span(2,1)},_],
|
|
87
|
-
# #['Trigger:', D+'trigSourceS', D+'trigCouplingS', D+'trigSlopeS', 'level:', D+'trigLevelS', 'delay:', {D+'trigDelay':span(2,1)},''],
|
|
88
|
-
# ['Trigger state:',D+'trigState',' trigMode:',D+'trigMode',
|
|
89
|
-
# 'TrigLevel','TrigDelay'],
|
|
90
|
-
# [{D+'trigger':color('lightCyan')}, D+'trigSource', D+'trigCoupling',
|
|
91
|
-
# D+'trigSlope', D+'trigLevel', D+'trigDelay'],
|
|
92
|
-
[{'ATTRIBUTES':color('lightGreen')}, 'Channels:','CH1','CH2','CH3','CH4','CH5','CH6'],
|
|
93
|
-
# ['Gain:']+ChLine('VoltsPerDiv'),
|
|
94
|
-
# ['Offset:']+ChLine('Position'),
|
|
95
|
-
# ['Coupling:']+ChLine('Coupling'),
|
|
96
|
-
# ['Termination:']+ChLine('Termination'),
|
|
97
|
-
# ['On/Off:']+ChLine('OnOff'),
|
|
98
|
-
#['Delay:']+ChLine('DelayFromTriggerM'),
|
|
99
|
-
#['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'),
|
|
100
84
|
['Mean:']+ChLine('Mean'),
|
|
101
85
|
['Peak2Peak:']+ChLine('Peak2Peak'),
|
|
102
|
-
#[''],
|
|
103
|
-
# ["Trigger",D+'trigSourceS',D+'trigLevelS',D+'trigSlopeS',D+'trigModeS'],
|
|
104
|
-
# ['',"Setup"],
|
|
105
|
-
# ["Repair:",D+'updateDataA',D+'deviceClearA',D+'resetScopeA',D+'forceTrigA'],
|
|
106
|
-
# ["Session",D+'SaveSession',D+'RecallSession',"folder:",D+'folderS'],
|
|
107
|
-
# [D+'currentSessionS',"<-current",D+'nextSessionS',"out off",D+'sessionsM'],
|
|
108
|
-
#[{'ATTRIBUTES':{'color':'yellow'}},
|
|
109
|
-
#['tAxis:',D+'tAxis'],
|
|
110
|
-
# [LYRow,'',{'For Experts only!':{**span(6,1),**font(14)}}],
|
|
111
|
-
# [LYRow,'Scope command:', {D+'instrCmdS':span(2,1)},_,{D+'instrCmdR':span(4,1)}],
|
|
112
|
-
# [LYRow,'Special commands', {D+'instrCtrl':span(2,1)},_,_,_,_,_,],
|
|
113
|
-
# [LYRow,'Timing:',{D+'timing':span(6,1)}],
|
|
114
|
-
# [LYRow,'ActOnEvent',D+'actOnEvent','AOE_Limit',D+'aOE_Limit',_,_,_],
|
|
115
86
|
]
|
|
@@ -1,11 +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
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.
|
|
6
8
|
|
|
7
9
|
import sys
|
|
8
10
|
import time
|
|
11
|
+
from time import perf_counter as timer
|
|
12
|
+
import os
|
|
9
13
|
from p4p.nt import NTScalar, NTEnum
|
|
10
14
|
from p4p.nt.enum import ntenum
|
|
11
15
|
from p4p.server import Server
|
|
@@ -105,7 +109,11 @@ def _create_PVs(pvDefs):
|
|
|
105
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"""
|
|
106
110
|
ts = time.time()
|
|
107
111
|
for defs in pvDefs:
|
|
108
|
-
|
|
112
|
+
try:
|
|
113
|
+
pname,desc,spv,extra = defs
|
|
114
|
+
except ValueError:
|
|
115
|
+
printe(f'Invalid PV definition of {defs[0]}')
|
|
116
|
+
sys.exit(1)
|
|
109
117
|
ivalue = spv.current()
|
|
110
118
|
printv(f'created pv {pname}, initial: {type(ivalue),ivalue}, extra: {extra}')
|
|
111
119
|
C_.PVs[C_.prefix+pname] = spv
|
|
@@ -203,7 +211,7 @@ def create_PVs(pvDefs=None):
|
|
|
203
211
|
U,LL,LH = 'units','limitLow','limitHigh'
|
|
204
212
|
C_.PVDefs = [
|
|
205
213
|
['version', 'Program version', SPV(__version__), {}],
|
|
206
|
-
['status', 'Server status', SPV('?','W'), {}],
|
|
214
|
+
['status', 'Server status. Features: RWE', SPV('?','W'), {}],
|
|
207
215
|
['server', 'Server control',
|
|
208
216
|
SPV('Start Stop Clear Exit Started Stopped Exited'.split(), 'WE'),
|
|
209
217
|
{'setter':set_server}],
|
|
@@ -218,80 +226,96 @@ def create_PVs(pvDefs=None):
|
|
|
218
226
|
_create_PVs(C_.PVDefs)
|
|
219
227
|
return C_.PVs
|
|
220
228
|
|
|
221
|
-
def get_externalPV(pvName, timeout=0.5):
|
|
222
|
-
"""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."""
|
|
223
232
|
ctxt = Context('pva')
|
|
224
233
|
return ctxt.get(pvName, timeout=timeout)
|
|
225
234
|
|
|
226
|
-
def init_epicsdev(prefix, pvDefs, verbose=0):
|
|
227
|
-
"""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
|
+
"""
|
|
228
241
|
C_.prefix = prefix
|
|
229
242
|
C_.verbose = verbose
|
|
230
243
|
try:
|
|
231
244
|
get_externalPV(prefix+'version')
|
|
232
|
-
print(f'Server for {prefix} already running. Exiting.')
|
|
245
|
+
print(f'ERROR: Server for {prefix} already running. Exiting.')
|
|
233
246
|
sys.exit(1)
|
|
234
247
|
except TimeoutError:
|
|
235
248
|
pass
|
|
236
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')
|
|
237
260
|
return pvs
|
|
238
261
|
|
|
239
262
|
#``````````````````Demo````````````````````````````````````````````````````````
|
|
240
263
|
if __name__ == "__main__":
|
|
264
|
+
print(f'epicsdev multiadc demo server {__version__}')
|
|
241
265
|
import numpy as np
|
|
242
266
|
import argparse
|
|
243
267
|
|
|
244
268
|
def myPVDefs():
|
|
245
269
|
"""Example of PV definitions"""
|
|
246
270
|
SET,U,LL,LH = 'setter','units','limitLow','limitHigh'
|
|
247
|
-
alarm = {'valueAlarm':{'lowAlarmLimit'
|
|
271
|
+
alarm = {'valueAlarm':{'lowAlarmLimit':-9., 'highAlarmLimit':9.}}
|
|
248
272
|
return [ # device-specific PVs
|
|
249
|
-
['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'}],
|
|
250
274
|
['tAxis', 'Full scale of horizontal axis', SPV([0.]), {U:'S'}],
|
|
251
275
|
['recordLength','Max number of points', SPV(100,'W','u32'),
|
|
252
276
|
{LL:4,LH:1000000, SET:set_recordLength}],
|
|
253
277
|
['ch1Offset', 'Offset', SPV(0.,'W'), {U:'du'}],
|
|
254
278
|
['ch1VoltsPerDiv', 'Vertical scale', SPV(1E-3,'W'), {U:'V/du'}],
|
|
255
|
-
['
|
|
256
|
-
['
|
|
257
|
-
['
|
|
258
|
-
['
|
|
259
|
-
['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}],
|
|
260
283
|
]
|
|
261
284
|
nPatterns = 100 # number of waveform patterns.
|
|
262
285
|
pargs = None
|
|
263
|
-
nDivs = 10 # number of divisions on the oscilloscope screen. That is needed to set tAxis when recordLength is changed.
|
|
264
286
|
rng = np.random.default_rng(nPatterns)
|
|
287
|
+
nPoints = 100
|
|
265
288
|
|
|
266
289
|
def set_recordLength(value):
|
|
267
290
|
"""Record length have changed. The tAxis should be updated accordingly."""
|
|
268
291
|
printi(f'Setting tAxis to {value}')
|
|
269
|
-
publish('tAxis', np.arange(value)*
|
|
292
|
+
publish('tAxis', np.arange(value)*1.E-6)
|
|
270
293
|
publish('recordLength', value)
|
|
271
294
|
set_noise(pvv('noiseLevel')) # Re-initialize noise array, because its size depends on recordLength
|
|
272
295
|
|
|
273
296
|
def set_noise(level):
|
|
274
297
|
"""Noise level have changed. Update noise array."""
|
|
275
|
-
|
|
298
|
+
v = float(level)
|
|
276
299
|
recordLength = pvv('recordLength')
|
|
277
|
-
|
|
278
|
-
|
|
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.')
|
|
279
303
|
publish('noiseLevel', level)
|
|
280
304
|
|
|
281
305
|
def init(recordLength):
|
|
282
306
|
"""Testing function. Do not use in production code."""
|
|
283
307
|
set_recordLength(recordLength)
|
|
284
|
-
set_noise(pvv('noiseLevel'))
|
|
308
|
+
#set_noise(pvv('noiseLevel')) # already called from set_recordLength
|
|
285
309
|
|
|
286
310
|
def poll():
|
|
287
311
|
"""Example of polling function"""
|
|
288
312
|
#pattern = C_.cycle % nPatterns# produces sliding
|
|
289
313
|
pattern = rng.integers(0, nPatterns)
|
|
290
|
-
|
|
291
|
-
printv(f'cycle {
|
|
292
|
-
publish('cycle',
|
|
314
|
+
cycle = pvv('cycle')
|
|
315
|
+
printv(f'cycle {repr(cycle)}')
|
|
316
|
+
publish('cycle', cycle + 1)
|
|
293
317
|
wf = pargs.noise[pattern:pattern+pvv('recordLength')].copy()
|
|
294
|
-
wf
|
|
318
|
+
wf /= pvv('ch1VoltsPerDiv')
|
|
295
319
|
wf += pvv('ch1Offset')
|
|
296
320
|
publish('ch1Waveform', wf)
|
|
297
321
|
publish('ch1Peak2Peak', np.ptp(wf))
|
|
@@ -301,22 +325,20 @@ if __name__ == "__main__":
|
|
|
301
325
|
parser = argparse.ArgumentParser(description = __doc__,
|
|
302
326
|
formatter_class=argparse.ArgumentDefaultsHelpFormatter,
|
|
303
327
|
epilog=f'{__version__}')
|
|
304
|
-
parser.add_argument('-l', '--
|
|
305
|
-
'
|
|
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.')
|
|
306
330
|
parser.add_argument('-p', '--prefix', default='epicsDev0:', help=
|
|
307
331
|
'Prefix to be prepended to all PVs')
|
|
308
|
-
|
|
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=
|
|
309
334
|
'Number of points in the waveform')
|
|
310
335
|
parser.add_argument('-v', '--verbose', action='count', default=0, help=
|
|
311
|
-
'Show more log messages (-vv: show even more)')
|
|
336
|
+
'Show more log messages (-vv: show even more)')
|
|
312
337
|
pargs = parser.parse_args()
|
|
338
|
+
print(pargs)
|
|
313
339
|
|
|
314
340
|
# Initialize epicsdev and PVs
|
|
315
|
-
PVs = init_epicsdev(pargs.prefix, myPVDefs(), pargs.verbose)
|
|
316
|
-
if pargs.listPVs:
|
|
317
|
-
print('List of PVs:')
|
|
318
|
-
for _pvname in PVs:
|
|
319
|
-
print(_pvname)
|
|
341
|
+
PVs = init_epicsdev(pargs.prefix, myPVDefs(), pargs.list, pargs.verbose)
|
|
320
342
|
|
|
321
343
|
# Initialize the device, using pargs if needed. That can be used to set the number of points in the waveform, for example.
|
|
322
344
|
init(pargs.npoints)
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|