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