epicsdev 3.1.1__tar.gz → 3.1.3__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.
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: epicsdev
3
- Version: 3.1.1
3
+ Version: 3.1.3
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
@@ -22,8 +22,13 @@ Helper module for building **EPICS PVAccess servers** using [p4p](https://github
22
22
  * Rapid PVAccess server development
23
23
  * High-rate data simulation and stress testing
24
24
  * GUI-based monitoring and control
25
+ * Rapid instrument integration
25
26
  * AI-assisted automatic device support generation
26
27
 
28
+ It integrates following EPICS IOC services:<br>
29
+ * **Autosave**: automatically saves the values of EPICS process variables (PVs) to files on a server host, and restores those values when the server restarts.
30
+ * **IocStats**: provides support for PVs that show the health and status of the server, plus a few control PVs.
31
+ * **caPutLog**: logging of PVAccess **`put`** operations.
27
32
  ---
28
33
 
29
34
  ## Installation
@@ -31,9 +36,6 @@ Helper module for building **EPICS PVAccess servers** using [p4p](https://github
31
36
  ```bash
32
37
  python -m pip install epicsdev
33
38
  ```
34
-
35
- ---
36
-
37
39
  ## Quick Demo
38
40
 
39
41
  Start the demo PVAccess server:
@@ -41,10 +43,7 @@ Start the demo PVAccess server:
41
43
  ```bash
42
44
  python -m epicsdev.epicsdev
43
45
  ```
44
-
45
- ---
46
-
47
- ## Control & Visualization
46
+ ### Control & Visualization
48
47
 
49
48
  Install optional GUI and plotting tools:
50
49
 
@@ -64,52 +63,53 @@ This provides:
64
63
  * Live waveform plots
65
64
  * Real-time parameter monitoring
66
65
 
67
- ---
66
+ The screenshots can be seen here: [control page](docs/epicsdev_pypet.png), [plots](docs/epicsdev_pvplot.jpg).
68
67
 
69
- # Multi-Channel Waveform Generator
68
+ ### Phoebus Display
70
69
 
71
- `epicsdev.multiadc` generates high-throughput synthetic data for stress-testing EPICS systems.
70
+ An example Phoebus display is provided: `config/epicsdev.bob`. [Screenshot](docs/phoebus_epicsdev.jpg).
72
71
 
73
- ### Example
72
+ ## Multi-Channel Waveform Generator
73
+
74
+ `epicsdev.multiadc` generates high-throughput synthetic data for stress-testing EPICS systems.
74
75
 
75
- Generate:
76
+ For example, the following command :
77
+ ```bash
78
+ python -m epicsdev.multiadc -s 0.1 -c 10000 -n 100
79
+ ```
80
+ Will start a server, which generates:
76
81
 
77
82
  * **10,000** noisy waveforms per second
78
83
  * **100 points per waveform**
79
84
  * **40,000 scalar parameters per second**
80
85
 
81
- ```bash
82
- python -m epicsdev.multiadc -s 0.1 -c 10000 -n 100
83
- ```
84
86
 
85
87
  ### Monitoring GUI
86
88
 
87
89
  ```bash
88
90
  python -m pypeto -c config -f multiadc
89
91
  ```
92
+ ## Text Put Logger
90
93
 
91
- The GUI includes:
94
+ `epicsdev.putlog` hosts a writable PV named `dump` and appends any written text to a file.
92
95
 
93
- * Control page
94
- * Real-time waveform plots<br>
95
- The screenshots can be seen here: [control page](docs/epicsdev_pypet.png), [plots](docs/epicsdev_pvplot.jpg).
96
- ---
96
+ Start the logger server (required argument: output file path):
97
97
 
98
- ## Phoebus Display
98
+ ```bash
99
+ python -m epicsdev.putlog /tmp/putlog.txt
100
+ ```
99
101
 
100
- An example Phoebus display is provided:
102
+ Default PV prefix is `putlog0:`, so write text to:
101
103
 
102
- ```
103
- config/epicsdev.bob<br>
104
+ ```bash
105
+ caput -p pva putlog0:dump "hello from client"
104
106
  ```
105
107
  ---
106
- [Screenshot](docs/phoebus_epicsdev.jpg)
107
-
108
- # AI-Assisted Device Support Development
108
+ ## AI-Assisted Device Support Development
109
109
 
110
110
  `epicsdev` is structured to enable automated server generation using AI tools such as GitHub Copilot.
111
111
 
112
- ## Workflow Example
112
+ ### Workflow Example
113
113
 
114
114
  1. Create a new GitHub repository.
115
115
 
@@ -127,24 +127,14 @@ config/epicsdev.bob<br>
127
127
 
128
128
  ### Real-World Example
129
129
 
130
- Using this method, a server implementation for a **Tektronix MSO oscilloscope** was:
130
+ Using this method, a server implementation for [Tektronix MSO oscilloscopes](https://github.com/ASukhanov/epicsdev_tektronix) was:
131
131
 
132
132
  * ~99% correct on first generation
133
133
  * Required only minor adjustments
134
134
 
135
135
  ---
136
136
 
137
- # Use Cases
138
-
139
- * EPICS PVAccess server prototyping
140
- * High-rate data simulation
141
- * Control system stress testing
142
- * Rapid instrument integration
143
- * AI-driven device support generation
144
-
145
- ---
146
-
147
- # Requirements
137
+ ## Requirements
148
138
 
149
139
  * Python 3.8+
150
140
  * p4p 4.2.2+
@@ -7,8 +7,13 @@ Helper module for building **EPICS PVAccess servers** using [p4p](https://github
7
7
  * Rapid PVAccess server development
8
8
  * High-rate data simulation and stress testing
9
9
  * GUI-based monitoring and control
10
+ * Rapid instrument integration
10
11
  * AI-assisted automatic device support generation
11
12
 
13
+ It integrates following EPICS IOC services:<br>
14
+ * **Autosave**: automatically saves the values of EPICS process variables (PVs) to files on a server host, and restores those values when the server restarts.
15
+ * **IocStats**: provides support for PVs that show the health and status of the server, plus a few control PVs.
16
+ * **caPutLog**: logging of PVAccess **`put`** operations.
12
17
  ---
13
18
 
14
19
  ## Installation
@@ -16,9 +21,6 @@ Helper module for building **EPICS PVAccess servers** using [p4p](https://github
16
21
  ```bash
17
22
  python -m pip install epicsdev
18
23
  ```
19
-
20
- ---
21
-
22
24
  ## Quick Demo
23
25
 
24
26
  Start the demo PVAccess server:
@@ -26,10 +28,7 @@ Start the demo PVAccess server:
26
28
  ```bash
27
29
  python -m epicsdev.epicsdev
28
30
  ```
29
-
30
- ---
31
-
32
- ## Control & Visualization
31
+ ### Control & Visualization
33
32
 
34
33
  Install optional GUI and plotting tools:
35
34
 
@@ -49,52 +48,53 @@ This provides:
49
48
  * Live waveform plots
50
49
  * Real-time parameter monitoring
51
50
 
52
- ---
51
+ The screenshots can be seen here: [control page](docs/epicsdev_pypet.png), [plots](docs/epicsdev_pvplot.jpg).
53
52
 
54
- # Multi-Channel Waveform Generator
53
+ ### Phoebus Display
55
54
 
56
- `epicsdev.multiadc` generates high-throughput synthetic data for stress-testing EPICS systems.
55
+ An example Phoebus display is provided: `config/epicsdev.bob`. [Screenshot](docs/phoebus_epicsdev.jpg).
57
56
 
58
- ### Example
57
+ ## Multi-Channel Waveform Generator
58
+
59
+ `epicsdev.multiadc` generates high-throughput synthetic data for stress-testing EPICS systems.
59
60
 
60
- Generate:
61
+ For example, the following command :
62
+ ```bash
63
+ python -m epicsdev.multiadc -s 0.1 -c 10000 -n 100
64
+ ```
65
+ Will start a server, which generates:
61
66
 
62
67
  * **10,000** noisy waveforms per second
63
68
  * **100 points per waveform**
64
69
  * **40,000 scalar parameters per second**
65
70
 
66
- ```bash
67
- python -m epicsdev.multiadc -s 0.1 -c 10000 -n 100
68
- ```
69
71
 
70
72
  ### Monitoring GUI
71
73
 
72
74
  ```bash
73
75
  python -m pypeto -c config -f multiadc
74
76
  ```
77
+ ## Text Put Logger
75
78
 
76
- The GUI includes:
79
+ `epicsdev.putlog` hosts a writable PV named `dump` and appends any written text to a file.
77
80
 
78
- * Control page
79
- * Real-time waveform plots<br>
80
- The screenshots can be seen here: [control page](docs/epicsdev_pypet.png), [plots](docs/epicsdev_pvplot.jpg).
81
- ---
81
+ Start the logger server (required argument: output file path):
82
82
 
83
- ## Phoebus Display
83
+ ```bash
84
+ python -m epicsdev.putlog /tmp/putlog.txt
85
+ ```
84
86
 
85
- An example Phoebus display is provided:
87
+ Default PV prefix is `putlog0:`, so write text to:
86
88
 
87
- ```
88
- config/epicsdev.bob<br>
89
+ ```bash
90
+ caput -p pva putlog0:dump "hello from client"
89
91
  ```
90
92
  ---
91
- [Screenshot](docs/phoebus_epicsdev.jpg)
92
-
93
- # AI-Assisted Device Support Development
93
+ ## AI-Assisted Device Support Development
94
94
 
95
95
  `epicsdev` is structured to enable automated server generation using AI tools such as GitHub Copilot.
96
96
 
97
- ## Workflow Example
97
+ ### Workflow Example
98
98
 
99
99
  1. Create a new GitHub repository.
100
100
 
@@ -112,24 +112,14 @@ config/epicsdev.bob<br>
112
112
 
113
113
  ### Real-World Example
114
114
 
115
- Using this method, a server implementation for a **Tektronix MSO oscilloscope** was:
115
+ Using this method, a server implementation for [Tektronix MSO oscilloscopes](https://github.com/ASukhanov/epicsdev_tektronix) was:
116
116
 
117
117
  * ~99% correct on first generation
118
118
  * Required only minor adjustments
119
119
 
120
120
  ---
121
121
 
122
- # Use Cases
123
-
124
- * EPICS PVAccess server prototyping
125
- * High-rate data simulation
126
- * Control system stress testing
127
- * Rapid instrument integration
128
- * AI-driven device support generation
129
-
130
- ---
131
-
132
- # Requirements
122
+ ## Requirements
133
123
 
134
124
  * Python 3.8+
135
125
  * p4p 4.2.2+
@@ -0,0 +1,45 @@
1
+ # Runnable Modules
2
+
3
+ This page lists the main runnable programs in `epicsdev`.
4
+
5
+ ## `epicsdev.epicsdev`
6
+
7
+ Generic/demo PVAccess server framework.
8
+
9
+ Run:
10
+
11
+ ```bash
12
+ python -m epicsdev.epicsdev
13
+ ```
14
+
15
+ ## `epicsdev.multiadc`
16
+
17
+ Multi-channel waveform generator for load and stress testing.
18
+
19
+ Run:
20
+
21
+ ```bash
22
+ python -m epicsdev.multiadc
23
+ ```
24
+
25
+ ## `epicsdev.putlog`
26
+
27
+ Hosts writable PV `dump`; every value written to it is appended to a file.
28
+
29
+ Run:
30
+
31
+ ```bash
32
+ python -m epicsdev.putlog /tmp/putlog.txt
33
+ ```
34
+
35
+ Default writable PV name:
36
+
37
+ ```text
38
+ putlog0:dump
39
+ ```
40
+
41
+ Example write:
42
+
43
+ ```bash
44
+ caput -p pva putlog0:dump "hello from client"
45
+ ```
@@ -0,0 +1,8 @@
1
+ """epicsdev package.
2
+
3
+ Main runnable modules:
4
+ - epicsdev.epicsdev : generic/demo PVAccess server framework
5
+ - epicsdev.multiadc : multi-channel waveform generator example
6
+ - epicsdev.putlog : writable `dump` PV that appends incoming text to a file
7
+ """
8
+
@@ -1,12 +1,13 @@
1
- """Skeleton and helper functions for creating EPICS PVAccess server"""
1
+ """Helper functions for creating EPICS PVAccess server"""
2
2
  # pylint: disable=invalid-name
3
- __version__= 'v3.1.1 26-03-03'# Autosave is implemented.
3
+ __version__= 'v3.1.3 26-03-04'# putlog functionality added, some refactoring, new features of epicsdev v3.1.0 used, some bugs fixed.
4
4
  # SPV removed, PvDefs definitions simplified, new features added.
5
5
  #TODO: add support for autosave, (feature 'A'), caputLog (feature 'H') and access rights
6
6
 
7
7
  import sys
8
8
  import time
9
9
  from time import perf_counter as timer
10
+ from datetime import datetime
10
11
  import os
11
12
  #import shelve
12
13
  import json
@@ -23,6 +24,7 @@ PeriodicUpdateInterval = 10. # seconds
23
24
  AutosaveInterval = 10. #
24
25
  AutosaveDefaultDirectory = '/operations/app_store/pvCache/' # Directory to save
25
26
  # autosave files. The actual file name will be <directory><prefix>.cache
27
+ IFace = Context('pva')# client context for getting values from other servers
26
28
 
27
29
  dtype2p4p = {# mapping from numpy dtype to p4p type code
28
30
  's8':'b', 'u8':'B', 's16':'h', 'u16':'H', 'i32':'i', 'u32':'I', 'i64':'l',
@@ -51,6 +53,7 @@ class C_():
51
53
  cachefd = None
52
54
  lastPutTime = time.time()# last time when a put operation was performed.
53
55
  lastAutosaveTime = 0.# last time when the cache was saved to a file.
56
+ putlogPV = None # name of the PV where put operations are logged. If None, then put operations are not logged.
54
57
 
55
58
  #```````````````````Helper methods````````````````````````````````````````````
56
59
  def serverState():
@@ -223,16 +226,17 @@ def create_PVs(pvDefs, pvcache=None):
223
226
  spv.name = pname
224
227
  spv.setter = extra.get('setter')
225
228
 
226
- # add put handler
229
+ # add a put handler
227
230
  @spv.put
228
231
  def handle(spv, op):
229
232
  vv = op.value()
230
233
  vr = vv.raw.value
231
234
  ntNamedTuples = spv._wrap(spv.current())
235
+ oldvr = ntNamedTuples['value']
232
236
  #print(f'Put request for {spv.name} = {repr(vv)}, current value: {repr(ntNamedTuples)}')
233
237
  # check limits, if they are defined. That will be a good
234
238
  # example of using control structure and valueAlarm.
235
- printv(f'Put request for {spv.name} = {repr(vr)}, value: {ntNamedTuples["value"]}')
239
+ #print(f'Put request for {spv.name} = {repr(vr)}, value: {ntNamedTuples["value"]}, peer: {op.name()}, {op.peer()}, {op.account()}, {op.roles()}')
236
240
  try:
237
241
  limitLow = ntNamedTuples['control.limitLow']
238
242
  limitHigh = ntNamedTuples['control.limitHigh']
@@ -253,6 +257,19 @@ def create_PVs(pvDefs, pvcache=None):
253
257
  ct = time.time()
254
258
  C_.lastPutTime = ct
255
259
  spv.post(vr, timestamp=ct) # update subscribers
260
+
261
+ if C_.putlogPV is not None:
262
+ dt = datetime.now().strftime('%Y-%m-%d %H:%M:%S.%f')[:-3].split()
263
+ ip = op.peer().split(':')[3][:-1]# peer looks like: [::ffff:192.168.27.6]:46362
264
+ jmsg = {"date":dt[0], "time":dt[1],
265
+ "host":ip, "user":op.account(),
266
+ "pv":op.name(), "new":vr, "old":oldvr}
267
+ s = json.dumps(jmsg)
268
+ try:
269
+ IFace.put(C_.putlogPV, "'"+s+"'", timeout=0.5)# quote the string to avoid interpreting it as JSON
270
+ except TimeoutError:
271
+ printw(f'WARNING: caPutLog feature will be disabled: PV {C_.putlogPV} not accessible.')
272
+ C_.putlogPV = None
256
273
  op.done()
257
274
  #,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,
258
275
  #``````````````````Setters
@@ -343,14 +360,8 @@ def create_pvDefs(pvDefs=None, pvcache=None):
343
360
  create_PVs(C_.PVDefs, pvcache)
344
361
  return C_.PVs
345
362
 
346
- def get_externalPV(pvName:str, timeout=0.5):
347
- """Get value of PV from another server. That can be used to check if the
348
- server is already running, or to get values from other servers."""
349
- ctxt = Context('pva')
350
- return ctxt.get(pvName, timeout=timeout)
351
-
352
- def init_epicsdev(prefix:str, pvDefs:list, verbose=0,
353
- serverStateChanged=None, listDir=None, autosaveDir=None, recall = True):
363
+ def init_epicsdev(prefix:str, pvDefs:list, verbose=0, serverStateChanged=None,
364
+ listDir=None, autosaveDir=None, recall = True, putlogPV=None):
354
365
  """Initialize epicsdev with given prefix and PV definitions.
355
366
  prefix is a string that will be prepended to all PV names. It should end with ':'.
356
367
  pvDefs is a list of PV definitions, each definition is a list of 3 or 4 items:
@@ -386,7 +397,7 @@ def init_epicsdev(prefix:str, pvDefs:list, verbose=0,
386
397
  if serverStateChanged is not None:# set custom serverStateChanged function
387
398
  C_.serverStateChanged = serverStateChanged
388
399
  try: # check if server is already running
389
- host = repr(get_externalPV(prefix+'HOSTNAME')).replace("'",'')
400
+ host = repr(IFace.get(prefix+'HOSTNAME', timeout=0.5)).replace("'",'')
390
401
  print(f'ERROR: Server for {prefix} already running at {host}. Exiting.')
391
402
  sys.exit(1)
392
403
  except TimeoutError:
@@ -430,6 +441,14 @@ def init_epicsdev(prefix:str, pvDefs:list, verbose=0,
430
441
  printi(f'Hosting {len(pvs)} PVs')
431
442
  C_.startTime = time.time()
432
443
 
444
+ try:
445
+ if putlogPV is not None:
446
+ _ = IFace.get(putlogPV, timeout=0.5)
447
+ C_.putlogPV = putlogPV
448
+ except TimeoutError:
449
+ printw(f'WARNING: caPutLog feature will not work: PV {putlogPV} not accessible.')
450
+ C_.putlogPV = None
451
+
433
452
  threading.Thread(target=_heartbeat_thread, daemon=True).start()
434
453
  return pvs
435
454
 
@@ -466,7 +485,8 @@ def sleep():
466
485
  C_.cycleTimeSum = 0.
467
486
  C_.cyclesAfterUpdate = 0
468
487
  sleeping = False
469
- if tnow - C_.lastAutosaveTime > AutosaveInterval:
488
+
489
+ if C_.cachefd is not None and tnow - C_.lastAutosaveTime > AutosaveInterval:
470
490
  C_.lastAutosaveTime = tnow
471
491
  if C_.lastPutTime != 0.:
472
492
  C_.lastPutTime = 0.
@@ -560,15 +580,17 @@ if __name__ == "__main__":
560
580
  # The rest of options are not essential, they can be controlled at runtime using PVs.
561
581
  parser.add_argument('-n', '--npoints', type=int, default=nPoints, help=
562
582
  'Number of points in the waveform')
583
+ parser.add_argument('-p', '--putlogPV', default='putlog:dump', help=
584
+ 'Name of the PV where put operations are logged. If None, then put operations are not logged.')
563
585
  parser.add_argument('-v', '--verbose', action='count', default=0, help=
564
586
  'Show more log messages (-vv: show even more)')
565
587
  pargs = parser.parse_args()
566
- printv(pargs)
588
+ print(pargs)
567
589
 
568
590
  # Initialize epicsdev and PVs
569
591
  pargs.prefix = f'{pargs.device}{pargs.index}:'
570
592
  PVs = init_epicsdev(pargs.prefix, myPVDefs(), pargs.verbose, None,
571
- pargs.list, pargs.autosave, pargs.recall)
593
+ pargs.list, pargs.autosave, pargs.recall, pargs.putlogPV)
572
594
  # Initialize the device using pargs if needed.
573
595
  init(pargs.npoints)
574
596
 
@@ -0,0 +1,70 @@
1
+ """PVAccess text logger server: writes text from PV `dump` to a file."""
2
+ # pylint: disable=invalid-name
3
+ __version__= 'v0.0.2 26-03-04'
4
+
5
+ import argparse
6
+ import threading
7
+
8
+ from .epicsdev import Server, init_epicsdev, publish, printi, set_server, serverState, sleep
9
+
10
+
11
+ class C_:
12
+ """Module-local storage."""
13
+ logfile = None
14
+ server = None
15
+ lock = threading.Lock()
16
+
17
+
18
+ def set_dump(value, *_):
19
+ """Append text written to `dump` PV into the selected log file."""
20
+ text = str(value)
21
+ with C_.lock:
22
+ C_.logfile.write(text)
23
+ if not text.endswith("\n"):
24
+ C_.logfile.write("\n")
25
+ C_.logfile.flush()
26
+ publish('dump', text)
27
+
28
+
29
+ def myPVDefs():
30
+ """PV definitions for putlog server."""
31
+ F = 'features'
32
+ T = 'type'
33
+ SET = 'setter'
34
+ return [
35
+ ['dump', 'Text to append to log file', '', {F: 'W', T: str, SET: set_dump}],
36
+ ]
37
+
38
+
39
+ def main():
40
+ """Program entry point."""
41
+ parser = argparse.ArgumentParser(
42
+ description=__doc__,
43
+ formatter_class=argparse.ArgumentDefaultsHelpFormatter,
44
+ )
45
+ parser.add_argument('logfile', nargs='?', default='/tmp/putlog.log', help=
46
+ 'Path to file where text written to the dump PV is appended.')
47
+ parser.add_argument('-p', '--prefix', default='putlog:', help='PV prefix')
48
+ parser.add_argument('-v', '--verbose', action='count', default=0, help=
49
+ 'Show more log messages (-vv: more).')
50
+ pargs = parser.parse_args()
51
+
52
+ C_.logfile = open(pargs.logfile, 'a', encoding='utf-8')
53
+
54
+ pvs = init_epicsdev(pargs.prefix, myPVDefs(), pargs.verbose, listDir='')
55
+
56
+ set_server('Start')
57
+
58
+ C_.server = Server(providers=[pvs])
59
+ printi(f'Server started with prefix {pargs.prefix}, writing dump text to {pargs.logfile}')
60
+ try:
61
+ while True:
62
+ if serverState().startswith('Exit'):
63
+ break
64
+ sleep()
65
+ finally:
66
+ C_.logfile.close()
67
+ printi('Server has exited')
68
+
69
+ if __name__ == '__main__':
70
+ main()
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
4
4
 
5
5
  [project]
6
6
  name = "epicsdev"
7
- version = "3.1.1"
7
+ version = "3.1.3"
8
8
  authors = [
9
9
  { name="Andrey Sukhanov", email="sukhanov@bnl.gov" },
10
10
  ]
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes