epicsdev 0.0.0__py3-none-any.whl → 1.0.0__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,123 +1,127 @@
1
1
  """Skeleton and helper functions for creating EPICS PVAccess server"""
2
2
  # pylint: disable=invalid-name
3
- __version__= 'v0.0.0 26-01-14'# Created
4
- #TODO: Do not start if another device is already running
3
+ __version__= 'v1.0.0 26-01-16'# re-factored and simplified, comments added. Main() re-writtedn.
5
4
  #TODO: NTEnums do not have structure display
6
- #TODO: Find a way to indicate that a PV is writable.
7
- # Options:
8
- # 1) add structure control with (0,0) limits as indication of Writable.
9
- # 2) use an extra field of the NTScalar.
10
5
 
11
- import argparse
6
+ import sys
12
7
  import time
13
8
  from p4p.nt import NTScalar, NTEnum
14
9
  from p4p.nt.enum import ntenum
15
10
  from p4p.server import Server
16
11
  from p4p.server.thread import SharedPV
12
+ from p4p.client.thread import Context
17
13
 
18
14
  #``````````````````Module Storage`````````````````````````````````````````````
19
15
  class C_():
20
16
  """Storage for module members"""
21
- AppName = 'epicsDevLecroyScope'
17
+ prefix = ''
18
+ verbose = 0
22
19
  cycle = 0
23
- lastRareUpdate = 0.
24
- server = None
25
20
  serverState = ''
26
21
  PVs = {}
27
22
  PVDefs = []
28
23
  #```````````````````Helper methods````````````````````````````````````````````
29
- def printTime(): return time.strftime("%m%d:%H%M%S")
30
- def printi(msg): print(f'inf_@{printTime()}: {msg}')
24
+ def serverState():
25
+ """Return current server state. That is the value of the server PV, but cached in C_ to avoid unnecessary get() calls."""
26
+ return C_.serverState
27
+ def _printTime():
28
+ return time.strftime("%m%d:%H%M%S")
29
+ def printi(msg):
30
+ """Print info message and publish it to status PV."""
31
+ print(f'inf_@{_printTime()}: {msg}')
31
32
  def printw(msg):
32
- txt = f'WAR_@{printTime()}: {msg}'
33
+ """Print warning message and publish it to status PV."""
34
+ txt = f'WAR_@{_printTime()}: {msg}'
33
35
  print(txt)
34
- #publish('status',txt)
36
+ publish('status',txt)
35
37
  def printe(msg):
36
- txt = f'ERR_{printTime()}: {msg}'
38
+ """Print error message and publish it to status PV."""
39
+ txt = f'ERR_{_printTime()}: {msg}'
37
40
  print(txt)
38
- #publish('status',txt)
41
+ publish('status',txt)
39
42
  def _printv(msg, level):
40
- if pargs.verbose >= level: print(f'DBG{level}: {msg}')
41
- def printv(msg): _printv(msg, 1)
42
- def printvv(msg): _printv(msg, 2)
43
- def printv3(msg): _printv(msg, 3)
43
+ if C_.verbose >= level:
44
+ print(f'DBG{level}: {msg}')
45
+ def printv(msg):
46
+ """Print debug message if verbosity level >=1."""
47
+ _printv(msg, 1)
48
+ def printvv(msg):
49
+ """Print debug message if verbosity level >=2."""
50
+ _printv(msg, 2)
51
+ def printv3(msg):
52
+ """Print debug message if verbosity level >=3."""
53
+ _printv(msg, 3)
44
54
 
45
- def pvobj(pvname):
55
+ def pvobj(pvName):
46
56
  """Return PV with given name"""
47
- return C_.PVs[pargs.prefix+pvname]
57
+ return C_.PVs[C_.prefix+pvName]
48
58
 
49
- def pvv(pvname:str):
59
+ def pvv(pvName:str):
50
60
  """Return PV value"""
51
- return pvobj(pvname).current()
61
+ return pvobj(pvName).current()
52
62
 
53
- def publish(pvname:str, value, ifChanged=False, t=None):
54
- """Post PV with new value"""
63
+ def publish(pvName:str, value, ifChanged=False, t=None):
64
+ """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."""
55
65
  try:
56
- pv = pvobj(pvname)
66
+ pv = pvobj(pvName)
57
67
  except KeyError:
68
+ printw(f'PV {pvName} not found. Cannot publish value.')
58
69
  return
59
70
  if t is None:
60
71
  t = time.time()
61
72
  if not ifChanged or pv.current() != value:
62
73
  pv.post(value, timestamp=t)
63
74
 
64
- def SPV(initial, vtype=None):
65
- """Construct SharedPV, vtype should be one of typeCode keys,
66
- if vtype is None then the nominal type will be determined automatically
75
+ def SPV(initial, meta='', vtype=None):
76
+ """Construct SharedPV.
77
+ meta is a string with characters W,A,E indicating if the PV is writable, has alarm or it is NTEnum.
78
+ vtype should be one of the p4p.nt type definitions (see https://epics-base.github.io/p4p/values.html).
79
+ if vtype is None then the nominal type will be determined automatically.
67
80
  """
68
81
  typeCode = {
69
- 'F64':'d', 'F32':'f', 'I64':'l', 'I8':'b', 'U8':'B', 'I16':'h',
70
- 'U16':'H', 'I32':'i', 'U32':'I', str:'s', 'enum':'enum',
82
+ 's8':'b', 'u8':'B', 's16':'h', 'u16':'H', 'i32':'i', 'u32':'I', 'i64':'l',
83
+ 'u64':'L', 'f32':'f', 'f64':'d', str:'s',
71
84
  }
72
85
  iterable = type(initial) not in (int,float,str)
73
86
  if vtype is None:
74
87
  firstItem = initial[0] if iterable else initial
75
88
  itype = type(firstItem)
76
- vtype = {int: 'I32', float: 'F32'}.get(itype,itype)
89
+ vtype = {int: 'i32', float: 'f32'}.get(itype,itype)
77
90
  tcode = typeCode[vtype]
78
- if tcode == 'enum':
91
+ if 'E' in meta:
79
92
  initial = {'choices': initial, 'index': 0}
80
- nt = NTEnum(display=True)#TODO: that does not work
93
+ nt = NTEnum(display=True, control='W' in meta)
81
94
  else:
82
95
  prefix = 'a' if iterable else ''
83
- nt = NTScalar(prefix+tcode, display=True, control=True, valueAlarm=True)
84
- return SharedPV(nt=nt, initial=initial)
85
-
86
- #``````````````````Definition of PVs``````````````````````````````````````````
87
- def _define_PVs():
88
- """Example of PV definitions"""
89
- R,W,SET,U,ENUM,LL,LH = 'R','W','setter','units','enum','limitLow','limitHigh'
90
- alarm = {'valueAlarm':{'lowAlarmLimit':0, 'highAlarmLimit':100}}
91
- return [
92
- # device-specific PVs
93
- ['VoltOffset', 'Offset', SPV(0.), W, {U:'V'}],
94
- ['VoltPerDiv', 'Vertical scale', SPV(0.), W, {U:'V/du'}],
95
- ['TimePerDiv', 'Horizontal scale', SPV('0.01 0.02 0.05 0.1 0.2 0.5 1 2 5'.split(),ENUM), W, {U:'S/du'}],
96
- ['trigDelay', 'Trigger delay', SPV(0.), W, {U:'S'}],
97
- ['Waveform', 'Waveform array', SPV([0.]), R, {}],
98
- ['tAxis', 'Full scale of horizontal axis', SPV([0.]), R, {}],
99
- ['recordLength','Max number of points', SPV(100,'U32'), W, {}],
100
- ['peak2peak', 'Peak-to-peak amplitude', SPV(0.), R, {}],
101
- ['alarm', 'PV with alarm', SPV(0), 'WA', alarm],
102
- ]
96
+ nt = NTScalar(prefix+tcode, display=True, control='W' in meta, valueAlarm='A' in meta)
97
+ pv = SharedPV(nt=nt, initial=initial)
98
+ pv.writable = 'W' in meta
99
+ return pv
103
100
 
104
101
  #``````````````````create_PVs()```````````````````````````````````````````````
105
- def _create_PVs():
106
- """Create PVs"""
102
+ def _create_PVs(pvDefs):
103
+ """Create PVs, using definitions from pvDEfs list. Each definition is a list of the form:
104
+ [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"""
107
105
  ts = time.time()
108
- for defs in C_.PVDefs:
109
- pname,desc,spv,features,extra = defs
110
- pv = spv
111
- ivalue = pv.current()
106
+ for defs in pvDefs:
107
+ pname,desc,spv,extra = defs
108
+ ivalue = spv.current()
112
109
  printv(f'created pv {pname}, initial: {type(ivalue),ivalue}, extra: {extra}')
113
- C_.PVs[pargs.prefix+pname] = pv
114
- #if isinstance(ivalue,dict):# NTEnum
110
+ C_.PVs[C_.prefix+pname] = spv
111
+ v = spv._wrap(ivalue, timestamp=ts)
112
+ if spv.writable:
113
+ try:
114
+ # 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.
115
+ v['control.limitLow'] = 0
116
+ v['control.limitHigh'] = 0
117
+ except KeyError as e:
118
+ #print(f'control not set for {pname}: {e}')
119
+ pass
115
120
  if 'ntenum' in str(type(ivalue)):
116
- pv.post(ivalue, timestamp=ts)
121
+ spv.post(ivalue, timestamp=ts)
117
122
  else:
118
- v = pv._wrap(ivalue, timestamp=ts)
119
123
  v['display.description'] = desc
120
- for field in extra.keys():
124
+ for field in extra.keys():
121
125
  if field in ['limitLow','limitHigh','format','units']:
122
126
  v[f'display.{field}'] = extra[field]
123
127
  if field.startswith('limit'):
@@ -125,34 +129,44 @@ def _create_PVs():
125
129
  if field == 'valueAlarm':
126
130
  for key,value in extra[field].items():
127
131
  v[f'valueAlarm.{key}'] = value
128
- pv.post(v)
132
+ spv.post(v)
129
133
 
130
134
  # add new attributes. To my surprise that works!
131
- pv.name = pname
132
- pv.setter = extra.get('setter')
135
+ spv.name = pname
136
+ spv.setter = extra.get('setter')
133
137
 
134
- writable = 'W' in features
135
- if writable:
136
- @pv.put
137
- def handle(pv, op):
138
+ if spv.writable:
139
+ @spv.put
140
+ def handle(spv, op):
138
141
  ct = time.time()
139
142
  vv = op.value()
140
143
  vr = vv.raw.value
144
+ current = spv._wrap(spv.current())
145
+ # check limits, if they are defined. That will be a good example of using control structure and valueAlarm.
146
+ try:
147
+ limitLow = current['control.limitLow']
148
+ limitHigh = current['control.limitHigh']
149
+ if limitLow != limitHigh and not (limitLow <= vr <= limitHigh):
150
+ printw(f'Value {vr} is out of limits [{limitLow}, {limitHigh}]. Ignoring.')
151
+ op.done(error=f'Value out of limits [{limitLow}, {limitHigh}]')
152
+ return
153
+ except KeyError:
154
+ pass
141
155
  if isinstance(vv, ntenum):
142
156
  vr = vv
143
- if pv.setter:
144
- pv.setter(vr)
145
- # value could change by the setter
146
- vr = pvv(pv.name)
147
- printv(f'putting {pv.name} = {vr}')
148
- pv.post(vr, timestamp=ct) # update subscribers
157
+ if spv.setter:
158
+ spv.setter(vr)
159
+ # value will be updated by the setter, so get it again
160
+ vr = pvv(spv.name)
161
+ printv(f'putting {spv.name} = {vr}')
162
+ spv.post(vr, timestamp=ct) # update subscribers
149
163
  op.done()
150
- #print(f'PV {pv.name} created: {pv}')
164
+ #print(f'PV {pv.name} created: {spv}')
151
165
  #,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,
152
166
  #``````````````````Setters
153
167
  def set_verbosity(level):
154
168
  """Set verbosity level for debugging"""
155
- pargs.verbose = level
169
+ C_.verbose = level
156
170
  publish('verbosity',level)
157
171
 
158
172
  def set_server(state=None):
@@ -164,72 +178,143 @@ def set_server(state=None):
164
178
  state = str(state)
165
179
  if state == 'Start':
166
180
  printi('Starting the server')
167
- #configure_scope()
168
- #adopt_local_setting()
181
+ # configure_instrument()
182
+ # adopt_local_setting()
169
183
  publish('server','Started')
184
+ publish('status','Started')
170
185
  elif state == 'Stop':
171
186
  printi('server stopped')
172
187
  publish('server','Stopped')
188
+ publish('status','Stopped')
173
189
  elif state == 'Exit':
174
190
  printi('server is exiting')
175
191
  publish('server','Exited')
192
+ publish('status','Exited')
176
193
  elif state == 'Clear':
177
194
  publish('acqCount', 0)
178
- #publish('lostTrigs', 0)
179
- #C_.triggersLost = 0
180
195
  publish('status','Cleared')
181
196
  # set server to previous state
182
197
  set_server(C_.serverState)
183
198
  C_.serverState = state
184
199
 
185
- def poll():
186
- """Example of polling function"""
187
- C_.cycle += 1
188
- printv(f'cycle {C_.cycle}')
189
- publish('cycle', C_.cycle)
190
-
191
- def create_PVs(pvDefs:list):
192
- """Creates manadatory PVs and adds PVs, using definitions from pvDEfs list"""
200
+ def create_PVs(pvDefs=None):
201
+ """Creates manadatory PVs and adds PVs specified in pvDefs list"""
193
202
  U,LL,LH = 'units','limitLow','limitHigh'
194
203
  C_.PVDefs = [
195
- ['version', 'Program version', SPV(__version__), 'R', {}],
196
- ['status', 'Server status', SPV('?'), 'W', {}],
204
+ ['version', 'Program version', SPV(__version__), {}],
205
+ ['status', 'Server status', SPV('?','W'), {}],
197
206
  ['server', 'Server control',
198
- SPV('Start Stop Clear Exit Started Stopped Exited'.split(), 'enum'),
199
- 'W', {'setter':set_server}],
200
- ['verbosity', 'Debugging verbosity', SPV(0,'U8'), 'W',
201
- {'setter':set_verbosity}],
202
- ['polling', 'Polling interval', SPV(1.0), 'W', {U:'S', LL:0.001, LH:10.1}],
203
- ['cycle', 'Cycle number', SPV(0,'U32'), 'R', {}],
207
+ SPV('Start Stop Clear Exit Started Stopped Exited'.split(), 'WE'),
208
+ {'setter':set_server}],
209
+ ['verbosity', 'Debugging verbosity', SPV(0,'W','u8'),
210
+ {'setter':set_verbosity}],
211
+ ['polling', 'Polling interval', SPV(1.0,'W'), {U:'S', LL:0.001, LH:10.1}],
212
+ ['cycle', 'Cycle number', SPV(0,'','u32'), {}],
204
213
  ]
205
- # append application PVs, defined in define_PVs()
206
- C_.PVDefs += pvDefs
207
- _create_PVs()
214
+ # append application's PVs, defined in the pvDefs and create map of providers
215
+ if pvDefs is not None:
216
+ C_.PVDefs += pvDefs
217
+ _create_PVs(C_.PVDefs)
208
218
  return C_.PVs
209
219
 
210
- #``````````````````Example of the Main() function````````````````````````````
220
+ def get_externalPV(pvName, timeout=0.5):
221
+ """Get value of PV from another server. That can be used to check if the server is already running, or to get values from other servers."""
222
+ ctxt = Context('pva')
223
+ return ctxt.get(pvName, timeout=timeout)
224
+
225
+ def init_epicsdev(prefix, pvDefs, verbose=0):
226
+ """Check if no other server is running with the same prefix, create PVs and return them as a dictionary."""
227
+ C_.prefix = prefix
228
+ C_.verbose = verbose
229
+ try:
230
+ get_externalPV(prefix+'version')
231
+ print(f'Server for {prefix} already running. Exiting.')
232
+ sys.exit(1)
233
+ except TimeoutError:
234
+ pass
235
+ pvs = create_PVs(pvDefs)
236
+ return pvs
237
+
238
+ #``````````````````Testing stuff``````````````````````````````````````````````
211
239
  if __name__ == "__main__":
240
+ import numpy as np
241
+ import argparse
242
+
243
+ def myPVDefs():
244
+ """Example of PV definitions"""
245
+ SET,U,LL,LH = 'setter','units','limitLow','limitHigh'
246
+ alarm = {'valueAlarm':{'lowAlarmLimit':0, 'highAlarmLimit':100}}
247
+ return [ # device-specific PVs
248
+ ['noiseLevel', 'Noise amplitude', SPV(1.E-6,'W'), {SET:set_noise}],
249
+ ['tAxis', 'Full scale of horizontal axis', SPV([0.]), {U:'S'}],
250
+ ['recordLength','Max number of points', SPV(100,'W','u32'),
251
+ {LL:4,LH:1000000, SET:set_recordLength}],
252
+ ['ch1Offset', 'Offset', SPV(0.,'W'), {U:'du'}],
253
+ ['ch1VoltsPerDiv', 'Vertical scale', SPV(1E-3,'W'), {U:'V/du'}],
254
+ ['timePerDiv', 'Horizontal scale', SPV(1.E-6,'W'), {U:'S/du'}],
255
+ ['ch1Waveform', 'Waveform array', SPV([0.]), {}],
256
+ ['ch1Mean', 'Mean of the waveform', SPV(0.,'A'), {}],
257
+ ['ch1Peak2Peak','Peak-to-peak amplitude', SPV(0.,'A'), {}],
258
+ ['alarm', 'PV with alarm', SPV(0,'WA'), alarm],
259
+ ]
260
+ nPatterns = 100 # number of patterns in the waveform.
261
+ pargs = None
262
+ nDivs = 10 # number of divisions on the oscilloscope screen. That is needed to set tAxis when recordLength is changed.
263
+
264
+ def set_recordLength(value):
265
+ """Record length have changed. The tAxis should be updated accordingly."""
266
+ printi(f'Setting tAxis to {value}')
267
+ publish('tAxis', np.arange(value)*pvv('timePerDiv')/nDivs)
268
+ publish('recordLength', value)
269
+
270
+ def set_noise(level):
271
+ """Noise level have changed. Update noise array."""
272
+ printi(f'Setting noise level to {level}')
273
+ pargs.noise = np.random.normal(scale=0.5*level, size=pargs.npoints+nPatterns)
274
+ #printv(f'Noise array updated with level {level}: {pargs.noise}')
275
+ publish('noiseLevel', level)
276
+
277
+ def init(recordLength):
278
+ """Testing function. Do not use in production code."""
279
+ set_recordLength(recordLength)
280
+ set_noise(pvv('noiseLevel'))
281
+
282
+ def poll():
283
+ """Example of polling function"""
284
+ pattern = C_.cycle % nPatterns
285
+ C_.cycle += 1
286
+ printv(f'cycle {C_.cycle}')
287
+ publish('cycle', C_.cycle)
288
+ wf = pargs.noise[pattern:pattern+pvv('recordLength')].copy()
289
+ wf *= pvv('ch1VoltsPerDiv')*nDivs
290
+ wf += pvv('ch1Offset')
291
+ publish('ch1Waveform', wf)
292
+ publish('ch1Peak2Peak', np.ptp(wf))
293
+ publish('ch1Mean', np.mean(wf))
294
+
212
295
  # Argument parsing
213
296
  parser = argparse.ArgumentParser(description = __doc__,
214
297
  formatter_class=argparse.ArgumentDefaultsHelpFormatter,
215
298
  epilog=f'{__version__}')
216
- parser.add_argument('-c','--channels', type=int, default=4, help=
217
- 'Number of channels in the scope')
218
- parser.add_argument('-p', '--prefix', default='epicsDev:', help=
219
- 'Prefix to be prepended to all PVs')
220
- parser.add_argument('-l', '--listPVs', action='store_true', help=\
221
- 'List all generated PVs')
222
- parser.add_argument('-v', '--verbose', action='count', default=0, help=\
223
- 'Show more log messages (-vv: show even more)')
299
+ parser.add_argument('-l', '--listPVs', action='store_true', help=
300
+ 'List all generated PVs')
301
+ parser.add_argument('-p', '--prefix', default='epicsDev0:', help=
302
+ 'Prefix to be prepended to all PVs')
303
+ parser.add_argument('-n', '--npoints', type=int, default=100, help=
304
+ 'Number of points in the waveform')
305
+ parser.add_argument('-v', '--verbose', action='count', default=0, help=
306
+ 'Show more log messages (-vv: show even more)')
224
307
  pargs = parser.parse_args()
225
308
 
226
- PVs = create_PVs(_define_PVs())# Provide your PV definitions instead of _define_PVs()
227
-
228
- # List the PVs
309
+ # Initialize epicsdev and PVs
310
+ PVs = init_epicsdev(pargs.prefix, myPVDefs(), pargs.verbose)
229
311
  if pargs.listPVs:
230
- print(f'List of PVs:')
231
- for pvname in PVs:
232
- print(pvname)
312
+ print('List of PVs:')
313
+ for _pvname in PVs:
314
+ print(_pvname)
315
+
316
+ # Initialize the device, using pargs if needed. That can be used to set the number of points in the waveform, for example.
317
+ init(pargs.npoints)
233
318
 
234
319
  # Start the Server. Use your set_server, if needed.
235
320
  set_server('Start')
@@ -237,8 +322,11 @@ if __name__ == "__main__":
237
322
  # Main loop
238
323
  server = Server(providers=[PVs])
239
324
  printi(f'Server started with polling interval {repr(pvv("polling"))} S.')
240
- while not C_.serverState.startswith('Exit'):
241
- time.sleep(pvv("polling"))
242
- if not C_.serverState.startswith('Stop'):
325
+ while True:
326
+ state = serverState()
327
+ if state.startswith('Exit'):
328
+ break
329
+ if not state.startswith('Stop'):
243
330
  poll()
331
+ time.sleep(pvv("polling"))
244
332
  printi('Server is exited')
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: epicsdev
3
- Version: 0.0.0
3
+ Version: 1.0.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
@@ -15,3 +15,17 @@ Description-Content-Type: text/markdown
15
15
 
16
16
  # epicsdev
17
17
  Helper module for creating EPICS PVAccess servers.
18
+
19
+ Demo:
20
+ ```
21
+ python pip install epicsdev
22
+ python -m epicsdev.epicsdev -l
23
+ ```
24
+
25
+ To control and plot:
26
+ ```
27
+ python pip install pypeto,pvplot
28
+ python -m pypeto -c config -f epicsdev
29
+ ```
30
+
31
+
@@ -0,0 +1,6 @@
1
+ epicsdev/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
2
+ epicsdev/epicsdev.py,sha256=iqXkOCwDA91IwIXf3a8ALyMstRLkX_TLTdsHwFS3SrA,13286
3
+ epicsdev-1.0.0.dist-info/METADATA,sha256=MiqIHi5DIXESKvq7_4Q1GYmlsyem0BEYnabLrERRTRM,786
4
+ epicsdev-1.0.0.dist-info/WHEEL,sha256=WLgqFyCfm_KASv4WHyYy0P3pM_m7J5L9k2skdKLirC8,87
5
+ epicsdev-1.0.0.dist-info/licenses/LICENSE,sha256=qj3cUKUrX4oXTb0NwuJQ44ThYDEMUfOeIjw9kkT6Qck,1072
6
+ epicsdev-1.0.0.dist-info/RECORD,,
@@ -1,6 +0,0 @@
1
- epicsdev/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
2
- epicsdev/epicsdev.py,sha256=BiiRZJw9CsmUO0arADg9ZKiiSv-uzvcoh02T6jQ7y_o,8913
3
- epicsdev-0.0.0.dist-info/METADATA,sha256=7JnJzpkOMPVVH4mqjv6wQBqTUmA_LHH-Jh6s9AWkRV8,608
4
- epicsdev-0.0.0.dist-info/WHEEL,sha256=WLgqFyCfm_KASv4WHyYy0P3pM_m7J5L9k2skdKLirC8,87
5
- epicsdev-0.0.0.dist-info/licenses/LICENSE,sha256=qj3cUKUrX4oXTb0NwuJQ44ThYDEMUfOeIjw9kkT6Qck,1072
6
- epicsdev-0.0.0.dist-info/RECORD,,