epicsdev 3.1.2__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.2
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,13 +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
 
27
28
  It integrates following EPICS IOC services:<br>
28
- * autoSave
29
- * iocStats
30
- * caputLog (work in progress)
31
-
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.
32
32
  ---
33
33
 
34
34
  ## Installation
@@ -36,9 +36,6 @@ It integrates following EPICS IOC services:<br>
36
36
  ```bash
37
37
  python -m pip install epicsdev
38
38
  ```
39
-
40
- ---
41
-
42
39
  ## Quick Demo
43
40
 
44
41
  Start the demo PVAccess server:
@@ -46,10 +43,7 @@ Start the demo PVAccess server:
46
43
  ```bash
47
44
  python -m epicsdev.epicsdev
48
45
  ```
49
-
50
- ---
51
-
52
- ## Control & Visualization
46
+ ### Control & Visualization
53
47
 
54
48
  Install optional GUI and plotting tools:
55
49
 
@@ -69,52 +63,53 @@ This provides:
69
63
  * Live waveform plots
70
64
  * Real-time parameter monitoring
71
65
 
72
- ---
66
+ The screenshots can be seen here: [control page](docs/epicsdev_pypet.png), [plots](docs/epicsdev_pvplot.jpg).
73
67
 
74
- # Multi-Channel Waveform Generator
68
+ ### Phoebus Display
75
69
 
76
- `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).
71
+
72
+ ## Multi-Channel Waveform Generator
77
73
 
78
- ### Example
74
+ `epicsdev.multiadc` generates high-throughput synthetic data for stress-testing EPICS systems.
79
75
 
80
- 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:
81
81
 
82
82
  * **10,000** noisy waveforms per second
83
83
  * **100 points per waveform**
84
84
  * **40,000 scalar parameters per second**
85
85
 
86
- ```bash
87
- python -m epicsdev.multiadc -s 0.1 -c 10000 -n 100
88
- ```
89
86
 
90
87
  ### Monitoring GUI
91
88
 
92
89
  ```bash
93
90
  python -m pypeto -c config -f multiadc
94
91
  ```
92
+ ## Text Put Logger
95
93
 
96
- The GUI includes:
94
+ `epicsdev.putlog` hosts a writable PV named `dump` and appends any written text to a file.
97
95
 
98
- * Control page
99
- * Real-time waveform plots<br>
100
- The screenshots can be seen here: [control page](docs/epicsdev_pypet.png), [plots](docs/epicsdev_pvplot.jpg).
101
- ---
96
+ Start the logger server (required argument: output file path):
102
97
 
103
- ## Phoebus Display
98
+ ```bash
99
+ python -m epicsdev.putlog /tmp/putlog.txt
100
+ ```
104
101
 
105
- An example Phoebus display is provided:
102
+ Default PV prefix is `putlog0:`, so write text to:
106
103
 
107
- ```
108
- config/epicsdev.bob<br>
104
+ ```bash
105
+ caput -p pva putlog0:dump "hello from client"
109
106
  ```
110
107
  ---
111
- [Screenshot](docs/phoebus_epicsdev.jpg)
112
-
113
- # AI-Assisted Device Support Development
108
+ ## AI-Assisted Device Support Development
114
109
 
115
110
  `epicsdev` is structured to enable automated server generation using AI tools such as GitHub Copilot.
116
111
 
117
- ## Workflow Example
112
+ ### Workflow Example
118
113
 
119
114
  1. Create a new GitHub repository.
120
115
 
@@ -132,24 +127,14 @@ config/epicsdev.bob<br>
132
127
 
133
128
  ### Real-World Example
134
129
 
135
- 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:
136
131
 
137
132
  * ~99% correct on first generation
138
133
  * Required only minor adjustments
139
134
 
140
135
  ---
141
136
 
142
- # Use Cases
143
-
144
- * EPICS PVAccess server prototyping
145
- * High-rate data simulation
146
- * Control system stress testing
147
- * Rapid instrument integration
148
- * AI-driven device support generation
149
-
150
- ---
151
-
152
- # Requirements
137
+ ## Requirements
153
138
 
154
139
  * Python 3.8+
155
140
  * p4p 4.2.2+
@@ -7,13 +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
 
12
13
  It integrates following EPICS IOC services:<br>
13
- * autoSave
14
- * iocStats
15
- * caputLog (work in progress)
16
-
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.
17
17
  ---
18
18
 
19
19
  ## Installation
@@ -21,9 +21,6 @@ It integrates following EPICS IOC services:<br>
21
21
  ```bash
22
22
  python -m pip install epicsdev
23
23
  ```
24
-
25
- ---
26
-
27
24
  ## Quick Demo
28
25
 
29
26
  Start the demo PVAccess server:
@@ -31,10 +28,7 @@ Start the demo PVAccess server:
31
28
  ```bash
32
29
  python -m epicsdev.epicsdev
33
30
  ```
34
-
35
- ---
36
-
37
- ## Control & Visualization
31
+ ### Control & Visualization
38
32
 
39
33
  Install optional GUI and plotting tools:
40
34
 
@@ -54,52 +48,53 @@ This provides:
54
48
  * Live waveform plots
55
49
  * Real-time parameter monitoring
56
50
 
57
- ---
51
+ The screenshots can be seen here: [control page](docs/epicsdev_pypet.png), [plots](docs/epicsdev_pvplot.jpg).
58
52
 
59
- # Multi-Channel Waveform Generator
53
+ ### Phoebus Display
60
54
 
61
- `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).
56
+
57
+ ## Multi-Channel Waveform Generator
62
58
 
63
- ### Example
59
+ `epicsdev.multiadc` generates high-throughput synthetic data for stress-testing EPICS systems.
64
60
 
65
- 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:
66
66
 
67
67
  * **10,000** noisy waveforms per second
68
68
  * **100 points per waveform**
69
69
  * **40,000 scalar parameters per second**
70
70
 
71
- ```bash
72
- python -m epicsdev.multiadc -s 0.1 -c 10000 -n 100
73
- ```
74
71
 
75
72
  ### Monitoring GUI
76
73
 
77
74
  ```bash
78
75
  python -m pypeto -c config -f multiadc
79
76
  ```
77
+ ## Text Put Logger
80
78
 
81
- The GUI includes:
79
+ `epicsdev.putlog` hosts a writable PV named `dump` and appends any written text to a file.
82
80
 
83
- * Control page
84
- * Real-time waveform plots<br>
85
- The screenshots can be seen here: [control page](docs/epicsdev_pypet.png), [plots](docs/epicsdev_pvplot.jpg).
86
- ---
81
+ Start the logger server (required argument: output file path):
87
82
 
88
- ## Phoebus Display
83
+ ```bash
84
+ python -m epicsdev.putlog /tmp/putlog.txt
85
+ ```
89
86
 
90
- An example Phoebus display is provided:
87
+ Default PV prefix is `putlog0:`, so write text to:
91
88
 
92
- ```
93
- config/epicsdev.bob<br>
89
+ ```bash
90
+ caput -p pva putlog0:dump "hello from client"
94
91
  ```
95
92
  ---
96
- [Screenshot](docs/phoebus_epicsdev.jpg)
97
-
98
- # AI-Assisted Device Support Development
93
+ ## AI-Assisted Device Support Development
99
94
 
100
95
  `epicsdev` is structured to enable automated server generation using AI tools such as GitHub Copilot.
101
96
 
102
- ## Workflow Example
97
+ ### Workflow Example
103
98
 
104
99
  1. Create a new GitHub repository.
105
100
 
@@ -117,24 +112,14 @@ config/epicsdev.bob<br>
117
112
 
118
113
  ### Real-World Example
119
114
 
120
- 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:
121
116
 
122
117
  * ~99% correct on first generation
123
118
  * Required only minor adjustments
124
119
 
125
120
  ---
126
121
 
127
- # Use Cases
128
-
129
- * EPICS PVAccess server prototyping
130
- * High-rate data simulation
131
- * Control system stress testing
132
- * Rapid instrument integration
133
- * AI-driven device support generation
134
-
135
- ---
136
-
137
- # Requirements
122
+ ## Requirements
138
123
 
139
124
  * Python 3.8+
140
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.2 26-03-03'# Do not autosave if --autosave is not handled.
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
 
@@ -561,15 +580,17 @@ if __name__ == "__main__":
561
580
  # The rest of options are not essential, they can be controlled at runtime using PVs.
562
581
  parser.add_argument('-n', '--npoints', type=int, default=nPoints, help=
563
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.')
564
585
  parser.add_argument('-v', '--verbose', action='count', default=0, help=
565
586
  'Show more log messages (-vv: show even more)')
566
587
  pargs = parser.parse_args()
567
- printv(pargs)
588
+ print(pargs)
568
589
 
569
590
  # Initialize epicsdev and PVs
570
591
  pargs.prefix = f'{pargs.device}{pargs.index}:'
571
592
  PVs = init_epicsdev(pargs.prefix, myPVDefs(), pargs.verbose, None,
572
- pargs.list, pargs.autosave, pargs.recall)
593
+ pargs.list, pargs.autosave, pargs.recall, pargs.putlogPV)
573
594
  # Initialize the device using pargs if needed.
574
595
  init(pargs.npoints)
575
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.2"
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