epicsdev_ps_caen_fastps 0.0.3__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.
File without changes
@@ -0,0 +1,375 @@
1
+ """EPICS PVAccess server for CAEN FAST-PS power supply."""
2
+ # pylint: disable=invalid-name,broad-exception-caught
3
+ __version__ = 'v0.0.3 2026-09-06'
4
+
5
+ import argparse
6
+ import re
7
+ import socket
8
+ import sys
9
+ import time
10
+
11
+ from epicsdev import epicsdev as edev
12
+
13
+ DEFAULT_HOST = '130.199.104.57'
14
+ DEFAULT_PORT = 10001
15
+ DEFAULT_TIMEOUT = 2.0
16
+ pargs = None
17
+ SetpointLimits = {#model: [minV, maxV, minI, maxI]
18
+ 'FAST-PS 0520-100': [-20.0, 20.0, -5.0, 5.0],
19
+ 'FAST-PS 0540-200': [-40.0, 40.0, -5.0, 5.0],
20
+ 'FAST-PS 1020-200': [-20.0, 20.0, -10.0, 10.0],
21
+ 'FAST-PS 0580-400': [-80.0, 80.0, -5.0, 5.0],
22
+ 'FAST-PS 1040-400': [-40.0, 40.0, -10.0, 10.0],
23
+ 'FAST-PS 2020-400': [-20.0, 20.0, -20.0, 20.0],
24
+ 'FAST-PS 2040-600': [-40.0, 40.0, -20.0, 20.0],
25
+ 'FAST-PS 3020-600': [-20.0, 20.0,-30.0, 30.0],
26
+ }
27
+
28
+ class C_:
29
+ """Namespace for module state."""
30
+ model = "/"
31
+ sock = None
32
+ PvDefs = []
33
+
34
+ def handle_exception(where: str):
35
+ """Log exceptions through status/error PVs."""
36
+ edev.printe(f'{where}: {sys.exc_info()[1]}')
37
+
38
+ def _connect():
39
+ """Open TCP socket to FAST-PS."""
40
+ try:
41
+ C_.sock = socket.create_connection((pargs.host, pargs.port), timeout=pargs.timeout)
42
+ C_.sock.settimeout(pargs.timeout)
43
+ edev.printi(f'Connected to FAST-PS at {pargs.host}:{pargs.port}')
44
+ except OSError:
45
+ handle_exception(f'connecting to {pargs.host}:{pargs.port}')
46
+ sys.exit(1)
47
+
48
+ def _read_line() -> str:
49
+ """Read one reply line from the socket."""
50
+ if C_.sock is None:
51
+ raise RuntimeError('Socket is not connected')
52
+
53
+ data = bytearray()
54
+ while True:
55
+ chunk = C_.sock.recv(1)
56
+ if not chunk:
57
+ break
58
+ data.extend(chunk)
59
+ if chunk == b'\n':
60
+ break
61
+ return data.decode('ascii', errors='ignore').strip()
62
+
63
+ def _send(cmd: str, updateStatus = False) -> str:
64
+ """Send ASCII command terminated with CR and return one-line reply."""
65
+ if C_.sock is None:
66
+ raise RuntimeError('Socket is not connected')
67
+
68
+ wire = f'{cmd}\r'.encode('ascii', errors='ignore')
69
+ edev.printv(f'Sending command: {cmd!r} -> {wire!r}, updateStatus={updateStatus}')
70
+ C_.sock.sendall(wire)
71
+ reply = _read_line()
72
+ if not reply:
73
+ raise RuntimeError(f'Empty reply for command {cmd!r}')
74
+ if updateStatus:
75
+ edev.publish('status', '')# clear status before sending command
76
+ _query_status()
77
+ return reply
78
+
79
+ def _is_ack(reply: str) -> bool:
80
+ r = reply.upper()
81
+ return '#AK' in r or r == 'AK'
82
+
83
+ def _parse_first_float(text: str):
84
+ m = re.search(r'[-+]?\d+(?:\.\d*)?(?:[eE][-+]?\d+)?', str(text))
85
+ return float(m.group(0)) if m else None
86
+
87
+ def _parse_status32(reply: str) -> int:
88
+ """Parse MST reply value (hex preferred, int fallback)."""
89
+ s = reply.strip()
90
+ if ':' in s:
91
+ s = s.split(':')[-1].strip()
92
+ s = s.replace('#', '').replace('0x', '').replace('0X', '').strip()
93
+
94
+ if re.fullmatch(r'[0-9A-Fa-f]+', s):
95
+ return int(s, 16)
96
+
97
+ m = re.search(r'\d+', s)
98
+ if m:
99
+ return int(m.group(0))
100
+ raise ValueError(f'Cannot parse status from reply {reply!r}')
101
+
102
+ def _query_float(cmd: str, default=None):
103
+ try:
104
+ reply = _send(cmd)
105
+ value = _parse_first_float(reply)
106
+ return default if value is None else value
107
+ except Exception:
108
+ handle_exception(f'in _query_float({cmd})')
109
+ return default
110
+
111
+ def _query_text(cmd: str, default='') -> str:
112
+ try:
113
+ return _send(cmd)
114
+ except Exception:
115
+ handle_exception(f'in _query_text({cmd})')
116
+ return default
117
+
118
+ def _query_status():
119
+ try:
120
+ reply = _send('MST')
121
+ status32 = _parse_status32(reply)
122
+ status_lsb = status32 & 0xFFFF
123
+ status_msb = (status32 >> 16) & 0xFFFF
124
+ edev.publish('StatusLSB', status_lsb, ifChanged=True)
125
+ edev.publish('StatusMSB', status_msb, ifChanged=True)
126
+ #edev.publish('_EnableInit', status_lsb, ifChanged=True)
127
+ enable = 1 if (status_lsb & 0x1) else 0
128
+ #edev.publish('_EnableInitCalc', enable, ifChanged=True)
129
+ edev.publish('Enable', enable, ifChanged=True)
130
+ reply = _send('UPMODE:?')
131
+ mode = reply.split(':')[-1].strip()
132
+ edev.publish('Upmode', mode, ifChanged=True)
133
+ reply = _send('MSRV:?')
134
+ rate_v = _parse_first_float(reply)
135
+ edev.publish('RampRateV', rate_v, ifChanged=True)
136
+ reply = _send('MSRI:?')
137
+ rate_i = _parse_first_float(reply)
138
+ edev.publish('RampRateI', rate_i, ifChanged=True)
139
+
140
+ except Exception:
141
+ handle_exception('in _query_status')
142
+
143
+ def set_regulation_mode(value, *_):
144
+ try:
145
+ mode = str(value).strip().upper()
146
+ mode = 'I' if mode.startswith('I') or mode == '1' else 'V'
147
+ reply = _send(f'LOOP {mode}', updateStatus=True)
148
+ if not _is_ack(reply):
149
+ raise RuntimeError(f'Unexpected reply for LOOP: {reply}')
150
+ edev.publish('RegulationMode', mode, ifChanged=True)
151
+ except Exception:
152
+ handle_exception('in set_regulation_mode')
153
+
154
+
155
+ def _set_setpoint(value: float, kind: str):
156
+ use_ramp = str(edev.pvv('RampEnable')).upper() in ('1', 'ON', 'TRUE')
157
+ cmd = f'MW{kind}R:{value}' if use_ramp else f'MW{kind}:{value}'
158
+ print(f'Setting {kind} setpoint to {value} (ramp: {use_ramp}, cmd: {cmd})')
159
+ reply = _send(cmd, updateStatus=True)
160
+ if not _is_ack(reply):
161
+ raise RuntimeError(f'Unexpected reply for {cmd}: {reply}')
162
+
163
+ def set_voltage(value, *_):
164
+ try:
165
+ voltage = float(value)
166
+ _set_setpoint(voltage, 'V')
167
+ edev.publish('Voltage', voltage, ifChanged=True)
168
+ except Exception:
169
+ handle_exception('in set_voltage')
170
+
171
+ def set_current(value, *_):
172
+ try:
173
+ current = float(value)
174
+ _set_setpoint(current, 'I')
175
+ edev.publish('Current', current, ifChanged=True)
176
+ except Exception:
177
+ handle_exception('in set_current')
178
+
179
+ def set_enable(value, *_):
180
+ try:
181
+ on = str(value).upper() in ('1', 'ON', 'TRUE')
182
+ cmd = 'MON' if on else 'MOFF'
183
+ reply = _send(cmd, updateStatus=True)
184
+ if not _is_ack(reply):
185
+ raise RuntimeError(f'Unexpected reply for {cmd}: {reply}')
186
+ edev.publish('Enable', 1 if on else 0, ifChanged=True)
187
+ except Exception:
188
+ handle_exception('in set_enable')
189
+
190
+
191
+ def set_status_reset(value, *_):
192
+ try:
193
+ v = str(value).upper()
194
+ if v in ('1', 'ON', 'TRUE'):
195
+ reply = _send('MRESET', updateStatus=True)
196
+ if not _is_ack(reply):
197
+ raise RuntimeError(f'Unexpected reply for MRESET: {reply}')
198
+ edev.publish('StatusReset', 0, ifChanged=True)
199
+ except Exception:
200
+ handle_exception('in set_status_reset')
201
+
202
+ def set_instrCmdS(cmd, *_):
203
+ try:
204
+ text = str(cmd).strip()
205
+ if text == '':
206
+ return
207
+ reply = _send(text, updateStatus=True)
208
+ edev.publish('instrCmdR', reply)
209
+ except Exception:
210
+ handle_exception('in set_instrCmdS')
211
+
212
+ def set_upmode(value, *_):
213
+ try:
214
+ mode = str(value).strip().upper()
215
+ if mode not in ('NORMAL', 'ANALOG', 'WAVEFORM', 'SFP'):
216
+ raise ValueError(f'Invalid update mode: {mode}')
217
+ reply = _send(f'UPMODE:{mode}', updateStatus=True)
218
+ if not _is_ack(reply):
219
+ raise RuntimeError(f'Unexpected reply for UPMODE: {reply}')
220
+ edev.publish('Upmode', mode, ifChanged=True)
221
+ except Exception:
222
+ handle_exception('in set_upmode')
223
+
224
+ def set_rampV_rate(value, *_):
225
+ try:
226
+ rate = float(value)
227
+ reply = _send(f'MSRV:{rate}', updateStatus=True)
228
+ if not _is_ack(reply):
229
+ raise RuntimeError(f'Unexpected reply for MSRV: {reply}')
230
+ edev.publish('RampRateV', rate, ifChanged=True)
231
+ except Exception:
232
+ handle_exception('in set_rampV_rate')
233
+
234
+ def set_rampI_rate(value, *_):
235
+ try:
236
+ rate = float(value)
237
+ reply = _send(f'MSRI:{rate}', updateStatus=True)
238
+ if not _is_ack(reply):
239
+ raise RuntimeError(f'Unexpected reply for MSRI: {reply}')
240
+ edev.publish('RampRateI', rate, ifChanged=True)
241
+ except Exception:
242
+ handle_exception('in set_rampI_rate')
243
+
244
+ def myPVDefs():
245
+ """PV definitions similar to ioc/fastps.db records."""
246
+ F, T, U, LL, LH, SET = 'features', 'type', 'units', 'limitLow', 'limitHigh', 'setter'
247
+
248
+ pv_defs = [
249
+ ['dateTime', 'Server local date/time', 'N/A'],
250
+ ['host', 'FAST-PS host', pargs.host],
251
+ ['port', 'FAST-PS TCP port', pargs.port, {T: 'u32'}],
252
+ ['RegulationMode', 'Selects between voltage/current regulation', ['V', 'I'], {F: 'WD', SET: set_regulation_mode}],
253
+ ['Voltage', 'Voltage control (V regulation mode)', 0.0, {F: 'W', U: 'V', SET: set_voltage}],
254
+ ['Current', 'Current control (I regulation mode)', 0.0, {F: 'W', U: 'A', SET: set_current}],
255
+ ['StatusReset', 'Reset status register / clear faults', 0, {F: 'W', T: 'u8', LL: 0, LH: 1, SET: set_status_reset}],
256
+ ['RampEnable', 'Enable/disable ramp to setpoint', ['Off', 'On'], {F: 'WD'}],
257
+ ['OutputVoltage', 'Output voltage', 0.0, {U: 'V'}],
258
+ ['OutputCurrent', 'Output current', 0.0, {U: 'A'}],
259
+ ['GroundCurrent', 'Ground current', 0.0, {U: 'A'}],
260
+ ['DCLinkVoltage', 'DC link voltage', 0.0, {U: 'V'}],
261
+ ['HeatsinkTemp', 'Heatsink temperature', 0.0, {U: 'C'}],
262
+ ['StatusMSB', 'Status MSB', 0, {T: 'u32'}],
263
+ ['StatusLSB', 'Status LSB', 0, {T: 'u32'}],
264
+ ['Limits', 'Voltage/current limits [MinV, MaxV, MinI, MaxI]', [0.0, 0.0, 0.0, 0.0]],
265
+ ['Model', 'Power supply model', 'N/A'],
266
+ ['Version', 'Power supply firmware version', 'N/A'],
267
+ ['Enable', 'Turn supply off/on', ['Off', 'On'], {F: 'WD', SET: set_enable}],
268
+ ['instrCmdS', 'Execute custom FAST-PS command', 'VER', {F: 'W', SET: set_instrCmdS}],
269
+ ['instrCmdR', 'Reply to custom FAST-PS command', ''],
270
+ ['ReadbackPoll_.SCAN', 'Readback polling period',# it is not necessary because the polling period is defined by 'sleep' PV, it is kept for compatibility with original IOC
271
+ ['1.0','0.5','0.2','0.1','0.01','2','5','10'], {F: 'WD', U: 's',
272
+ SET: lambda v, *_: edev.publish('sleep', float(v), ifChanged=True)}],
273
+ ['Upmode', 'Update mode', ['NORMAL', 'ANALOG', 'WAVEFORM', 'SFP'], {F: 'WD', SET: set_upmode}],
274
+ ['RampRateV', 'Ramp rate V/s', 1.0, {F: 'W', U: 'V/s', LL:1e-6, LH:500.0, SET:set_rampV_rate}],
275
+ ['RampRateI', 'Ramp rate I/s', 1.0, {F: 'W', U: 'A/s', LL:1e-6, LH:500.0, SET:set_rampI_rate}],
276
+ ]
277
+ return pv_defs
278
+
279
+ def refresh_static():
280
+ """Read static identification and setpoint values."""
281
+ ver = _query_text('VER', 'N/A')
282
+ C_.model = ver.split(':')[1]
283
+ edev.publish('Model', C_.model)
284
+ fw = ver.rsplit(':',1)[-1]
285
+ edev.publish('Version', fw)
286
+ edev.publish('Limits', SetpointLimits[C_.model])
287
+
288
+ loop = _query_text('LOOP ?', 'V').upper()
289
+ edev.publish('RegulationMode', 'I' if 'I' in loop else 'V', ifChanged=True)
290
+
291
+ edev.publish('Voltage', _query_float('MWV ?', 0.0), ifChanged=True)
292
+ edev.publish('Current', _query_float('MWI ?', 0.0), ifChanged=True)
293
+
294
+ def poll():
295
+ """Main polling hook."""
296
+ edev.publish('OutputVoltage', _query_float('MRV', edev.pvv('OutputVoltage')), ifChanged=True)
297
+ edev.publish('OutputCurrent', _query_float('MRI', edev.pvv('OutputCurrent')), ifChanged=True)
298
+ edev.publish('GroundCurrent', _query_float('MGC', edev.pvv('GroundCurrent')), ifChanged=True)
299
+ edev.publish('DCLinkVoltage', _query_float('MRP', edev.pvv('DCLinkVoltage')), ifChanged=True)
300
+ edev.publish('HeatsinkTemp', _query_float('MRT', edev.pvv('HeatsinkTemp')), ifChanged=True)
301
+
302
+ def periodic_update():
303
+ """Slow periodic update hook."""
304
+ #print('Periodic update')
305
+ edev.publish('dateTime', time.strftime('%Y-%m-%d %H:%M:%S'), ifChanged=True)
306
+ _query_status()
307
+
308
+ def serverStateChanged(newState: str):
309
+ """Callback for server state transitions."""
310
+ if newState == 'Start':
311
+ edev.printi('Start requested')
312
+ refresh_static()
313
+ _query_status()
314
+ elif newState == 'Stop':
315
+ edev.printi('Stop requested')
316
+ elif newState == 'Exit':
317
+ edev.printi('Exit requested')
318
+
319
+ if __name__ == '__main__':
320
+ parser = argparse.ArgumentParser(
321
+ description=__doc__,
322
+ formatter_class=argparse.ArgumentDefaultsHelpFormatter,
323
+ epilog=__version__,
324
+ )
325
+ parser.add_argument('-a', '--autosave', nargs='?', default='', help='Autosave control. If omitted, autosave is enabled with default directory.')
326
+ parser.add_argument('-c', '--recall', action='store_false', help='If given: do not restore initial PV values from autosave cache.')
327
+ parser.add_argument('-d', '--device', default='caen_fastps', help='Device name, the PV prefix is <device><index>:')
328
+ parser.add_argument('-i', '--index', default='0', help='Device index, the PV prefix is <device><index>:')
329
+ parser.add_argument('-p', '--putlogPV', nargs='?', default='', help='PV name for logging put operations. Empty means default putlog:dump.')
330
+ parser.add_argument('-v', '--verbose', action='count', default=0, help='Increase verbosity (-vv for more).')
331
+
332
+ parser.add_argument('--host', default=DEFAULT_HOST, help='FAST-PS host name or IP address')
333
+ parser.add_argument('--port', type=int, default=DEFAULT_PORT, help='FAST-PS TCP port')
334
+ parser.add_argument('--timeout', type=float, default=DEFAULT_TIMEOUT, help='TCP timeout in seconds')
335
+
336
+ pargs = parser.parse_args()
337
+ if pargs.putlogPV == '':
338
+ pargs.putlogPV = 'putlog:dump'
339
+ pargs.prefix = f'{pargs.device}{pargs.index}:'
340
+
341
+ _connect()
342
+ C_.PvDefs = myPVDefs()
343
+
344
+ PVs = edev.init_epicsdev(
345
+ pargs.prefix,
346
+ C_.PvDefs,
347
+ pargs.verbose,
348
+ serverStateChanged,
349
+ '',
350
+ pargs.autosave,
351
+ pargs.recall,
352
+ pargs.putlogPV,
353
+ )
354
+
355
+ edev.publish('VERSION', __version__)
356
+ edev.set_server('Start')
357
+
358
+ server = edev.Server(providers=[PVs])
359
+ edev.printi(f'Server for {pargs.prefix} started. Sleeping per cycle: {repr(edev.pvv("sleep"))} S.')
360
+ while True:
361
+ state = edev.serverState()
362
+ if state.startswith('Exit'):
363
+ break
364
+ if not state.startswith('Stop'):
365
+ poll()
366
+ if not edev.sleep():
367
+ periodic_update()
368
+
369
+ try:
370
+ if C_.sock is not None:
371
+ C_.sock.close()
372
+ except OSError:
373
+ pass
374
+
375
+ edev.printi('Server is exited')
@@ -0,0 +1,113 @@
1
+ Metadata-Version: 2.5
2
+ Name: epicsdev_ps_caen_fastps
3
+ Version: 0.0.3
4
+ Summary: EPICS PVAccess server for CAEN FAST-PS power supplies
5
+ Author-email: Andrey Sukhanov <sukhanov@bnl.gov>
6
+ License-File: LICENSE
7
+ Classifier: License :: OSI Approved :: MIT License
8
+ Classifier: Operating System :: OS Independent
9
+ Classifier: Programming Language :: Python :: 3
10
+ Requires-Python: >=3.10
11
+ Requires-Dist: epicsdev>=1.0.2
12
+ Provides-Extra: opi
13
+ Requires-Dist: phoebusgen; extra == 'opi'
14
+ Description-Content-Type: text/markdown
15
+
16
+ # epicsdev_ps_caen_fastps
17
+
18
+ EPICS PVAccess server for CAEN FAST-PS power supplies, implemented with `epicsdev`.
19
+
20
+ Main server module: [caen_fastps/__main__.py](caen_fastps/__main__.py)
21
+ IOC reference DB: [ioc/fastps.db](ioc/fastps.db)
22
+ Phoebus screen generator: [opi/generate_screen.py](screens/generate_screen.py)
23
+ - Main server module: [epicsdev_ps_caen_fastps/__main__.py](epicsdev_ps_caen_fastps/__main__.py)
24
+ - IOC reference DB: [docs/fastps.db](docs/fastps.db)
25
+ - Phoebus screen generator: [opi/generate_opi.py](opi/generate_opi.py)
26
+
27
+ ## Features
28
+
29
+ - TCP remote control interface to FAST-PS (default port `10001`)
30
+ - PV set modeled after the IOC records in [docs/fastps.db](docs/fastps.db)
31
+ - Setpoint control with optional ramping:
32
+ - `Voltage`, `Current`, `RampEnable`
33
+ - Output and diagnostic readback:
34
+ - `OutputVoltage`, `OutputCurrent`, `GroundCurrent`, `DCLinkVoltage`, `HeatsinkTemp`
35
+ - Status and state control:
36
+ - `StatusMSB`, `StatusLSB`, `StatusReset`, `Enable`, `RegulationMode`, `Upmode`
37
+ - Ramp-rate control:
38
+ - `RampRateV`, `RampRateI`
39
+ - Device identity and inferred limits:
40
+ - `Model`, `Version`, `Limits`
41
+ - Generic command passthrough:
42
+ - `instrCmdS`, `instrCmdR`
43
+
44
+ ## FAST-PS protocol mapping
45
+
46
+ Implemented command families:
47
+
48
+ - `VER`
49
+ - `MON`, `MOFF`
50
+ - `LOOP`, `LOOP ?`
51
+ - `MWV`, `MWV ?`, `MWVR`
52
+ - `MWI`, `MWI ?`, `MWIR`
53
+ - `MRESET`
54
+ - `MST`
55
+ - `UPMODE:?`, `UPMODE:<mode>`
56
+ - `MSRV:?`, `MSRV:<value>`
57
+ - `MSRI:?`, `MSRI:<value>`
58
+ - `MRV`, `MRI`, `MGC`, `MRP`, `MRT`
59
+
60
+ ## Requirements
61
+
62
+ - Python 3.10+
63
+ - `epicsdev` and its runtime dependencies (including `p4p`)
64
+ - Network access to the CAEN FAST-PS device
65
+
66
+ ## Install and run
67
+
68
+ - `pip install epicsdev_ps_caen_fastps
69
+ - `python -m epicsdev_ps_caen_fastps`
70
+
71
+ Useful arguments:
72
+
73
+ - `--host` FAST-PS IP/hostname (default: `130.199.104.57`)
74
+ - `--port` TCP port (default: `10001`)
75
+ - `--timeout` socket timeout in seconds (default: `2.0`)
76
+ - `-d, --device` PV prefix device root (default: `caen_fastps`)
77
+ - `-i, --index` PV prefix index (default: `0`)
78
+ - `-v` increase verbosity (`-vv` for more)
79
+ - `-a, --autosave` enable autosave with optional directory
80
+ - `-c, --recall` disable restoring autosaved values on startup
81
+
82
+ Example:
83
+
84
+ - `python -m epicsdev_ps_caen_fastps --host 130.199.104.57 -d caen_fastps:`
85
+
86
+ Default PV prefix:
87
+
88
+ - `caen_fastps0:`
89
+
90
+ ## Generate a Phoebus screen
91
+
92
+ Generate `.bob` OPI file:
93
+
94
+ - `python generate_opi.py -t FAST-PS pva://caen_fastps:0:`
95
+
96
+ Options:
97
+
98
+ - `-t, --title` screen title
99
+ - `prefix` PV prefix macro/value (default: `$(DEV):`)
100
+
101
+ Output:
102
+
103
+ - [opi/caen_fastps.bob](opi/caen_fastps.bob)
104
+
105
+ ## Screenshot
106
+
107
+ - [docs/opi_caen_fastps.jpg](docs/opi_caen_fastps.jpg)
108
+
109
+ ## Notes
110
+
111
+ - The server uses common `epicsdev` control PVs such as `server`, `sleep`, `status`, and `HEARTBEAT`.
112
+ - `Enable` is synchronized from bit 0 of `StatusLSB` during polling.
113
+ - For unsupported/custom diagnostics, use `instrCmdS` and read the reply from `instrCmdR`.
@@ -0,0 +1,6 @@
1
+ epicsdev_ps_caen_fastps/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
2
+ epicsdev_ps_caen_fastps/__main__.py,sha256=eZ_0wNLz6Vl30Tt_t8E9MzXO_Ar223qvEGUUEzkcmDw,14394
3
+ epicsdev_ps_caen_fastps-0.0.3.dist-info/METADATA,sha256=OPQ9dmvVRO3l_ozlTDI0m0StYnLDYlKE5DZCNtl290I,3404
4
+ epicsdev_ps_caen_fastps-0.0.3.dist-info/WHEEL,sha256=zOwg4jB6zX2kU910N-cMawjivD6tO8NEWvE12je1bVk,87
5
+ epicsdev_ps_caen_fastps-0.0.3.dist-info/licenses/LICENSE,sha256=b05363Mlkgmk87PGUA5VCSerlC-TK2Q1GaP3o33qCLY,1508
6
+ epicsdev_ps_caen_fastps-0.0.3.dist-info/RECORD,,
@@ -0,0 +1,4 @@
1
+ Wheel-Version: 1.0
2
+ Generator: hatchling 1.32.0
3
+ Root-Is-Purelib: true
4
+ Tag: py3-none-any
@@ -0,0 +1,28 @@
1
+ BSD 3-Clause License
2
+
3
+ Copyright (c) 2026, Electron-Ion Collider
4
+
5
+ Redistribution and use in source and binary forms, with or without
6
+ modification, are permitted provided that the following conditions are met:
7
+
8
+ 1. Redistributions of source code must retain the above copyright notice, this
9
+ list of conditions and the following disclaimer.
10
+
11
+ 2. Redistributions in binary form must reproduce the above copyright notice,
12
+ this list of conditions and the following disclaimer in the documentation
13
+ and/or other materials provided with the distribution.
14
+
15
+ 3. Neither the name of the copyright holder nor the names of its
16
+ contributors may be used to endorse or promote products derived from
17
+ this software without specific prior written permission.
18
+
19
+ THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
20
+ AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
21
+ IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
22
+ DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
23
+ FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
24
+ DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
25
+ SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
26
+ CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
27
+ OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
28
+ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.