Skip to content

Commit 3b0bef0

Browse files
authored
Autosave support (#163)
Add support for saving and restoring pv fields with autosave
1 parent dcccdd6 commit 3b0bef0

File tree

16 files changed

+1181
-21
lines changed

16 files changed

+1181
-21
lines changed

.github/workflows/code.yml

Lines changed: 24 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -17,10 +17,10 @@ jobs:
1717
runs-on: "ubuntu-latest"
1818
steps:
1919
- name: Checkout Source
20-
uses: actions/checkout@v2
20+
uses: actions/checkout@v4
2121

2222
- name: Install Python
23-
uses: actions/setup-python@v2
23+
uses: actions/setup-python@v4
2424
with:
2525
python-version: "3.7"
2626

@@ -34,7 +34,7 @@ jobs:
3434
runs-on: "ubuntu-latest"
3535
steps:
3636
- name: Checkout Source
37-
uses: actions/checkout@v2
37+
uses: actions/checkout@v4
3838
with:
3939
# require history to get back to last tag for version number of branches
4040
fetch-depth: 0
@@ -44,7 +44,7 @@ jobs:
4444
run: pipx run build --sdist .
4545

4646
- name: Upload Sdist
47-
uses: actions/upload-artifact@v2
47+
uses: actions/upload-artifact@v4
4848
with:
4949
name: dist
5050
path: dist/*
@@ -56,6 +56,11 @@ jobs:
5656
os: [ubuntu-latest, windows-latest, macos-latest]
5757
python: [cp37, cp38, cp39, cp310]
5858

59+
exclude:
60+
# MacOS 14.4.1 for arm64 doesn't support Python < 3.8
61+
- os: macos-latest
62+
python: "cp37"
63+
5964
include:
6065
# Put coverage and results files in the project directory for mac
6166
- os: macos-latest
@@ -69,22 +74,27 @@ jobs:
6974
- os: ubuntu-latest
7075
cov_file: /output/coverage.xml
7176
results_file: /output/pytest-results.xml
77+
# MacOS 13 required for Python < 3.8
78+
- os: macos-13
79+
python: "cp37"
80+
cov_file: "{project}/dist/coverage.xml"
81+
results_file: "{project}/dist/pytest-results.xml"
7282

7383
name: build/${{ matrix.os }}/${{ matrix.python }}
7484
runs-on: ${{ matrix.os }}
7585

7686
steps:
7787
- name: Checkout Source
78-
uses: actions/checkout@v2
88+
uses: actions/checkout@v4
7989
with:
8090
# require history to get back to last tag for version number of branches
8191
fetch-depth: 0
8292
submodules: true
8393

8494
- name: Install Python
85-
uses: actions/setup-python@v2
95+
uses: actions/setup-python@v4
8696
with:
87-
python-version: "3.7"
97+
python-version: "3.12"
8898

8999
- name: Install Python Dependencies
90100
# Pin cibuildwheel due to https://github.com/pypa/cibuildwheel/issues/962
@@ -106,20 +116,20 @@ jobs:
106116
CIBW_SKIP: "*-musllinux*" # epicscorelibs doesn't build on musllinux platforms
107117

108118
- name: Upload Wheel
109-
uses: actions/upload-artifact@v2
119+
uses: actions/upload-artifact@v4
110120
with:
111-
name: dist
121+
name: dist-${{ matrix.os }}-${{ matrix.python }}
112122
path: dist/softioc*
113123

114124
- name: Upload coverage to Codecov
115-
uses: codecov/codecov-action@v2
125+
uses: codecov/codecov-action@v4
116126
with:
117127
name: ${{ matrix.os }}/${{ matrix.python }}
118128
directory: dist
119129

120130
- name: Upload Unit Test Results
121131
if: always()
122-
uses: actions/upload-artifact@v2
132+
uses: actions/upload-artifact@v4
123133
with:
124134
name: Unit Test Results (${{ matrix.os }}-${{ matrix.python }})
125135
path: dist/pytest-results.xml
@@ -132,7 +142,7 @@ jobs:
132142

133143
steps:
134144
- name: Download Artifacts
135-
uses: actions/download-artifact@v2
145+
uses: actions/download-artifact@v4
136146
with:
137147
path: artifacts
138148

@@ -152,7 +162,7 @@ jobs:
152162
runs-on: ${{ matrix.os }}
153163

154164
steps:
155-
- uses: actions/download-artifact@v2
165+
- uses: actions/download-artifact@v4
156166
with:
157167
name: dist
158168
path: dist
@@ -167,7 +177,7 @@ jobs:
167177
# upload to PyPI and make a release on every tag
168178
if: github.event_name == 'push' && startsWith(github.event.ref, 'refs/tags')
169179
steps:
170-
- uses: actions/download-artifact@v2
180+
- uses: actions/download-artifact@v4
171181
with:
172182
name: dist
173183
path: dist

CHANGELOG.rst

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ Unreleased_
1111
-----------
1212

1313
Added:
14-
14+
- `Add autosave support to all records and record fields <../../pull/163>`_
1515
- `Add int64In/Out record support <../../pull/161>`_
1616
- `Enable setting alarm status of Out records <../../pull/157>`_
1717
- `Adding the non_interactive_ioc function <../../pull/156>`_

Pipfile.lock

Lines changed: 90 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.
Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
from softioc import autosave, builder, softioc
2+
import cothread
3+
4+
# Set the record prefix
5+
builder.SetDeviceName("MY-DEVICE-PREFIX")
6+
7+
# Create records, set some of them to autosave, also save some of their fields
8+
9+
builder.aOut("AO", autosave=True)
10+
builder.aIn("AI", autosave=["PREC", "EGU"])
11+
builder.boolIn("BO")
12+
builder.WaveformIn("WAVEFORMIN", [0, 0, 0, 0], autosave=True)
13+
with autosave.Autosave(["VAL", "LOPR", "HOPR"]):
14+
builder.aOut("AUTOMATIC-AO", autosave=["EGU"])
15+
seconds = builder.longOut("SECONDSRUN", autosave=True)
16+
17+
autosave.configure(
18+
directory="/tmp/autosave-data",
19+
name="MY-DEVICE-PREFIX",
20+
save_period=5.0
21+
)
22+
23+
builder.LoadDatabase()
24+
softioc.iocInit()
25+
26+
# Start processes required to be run after iocInit
27+
def update():
28+
while True:
29+
cothread.Sleep(1)
30+
seconds.set(seconds.get() + 1)
31+
32+
cothread.Spawn(update)
33+
34+
softioc.interactive_ioc(globals())
Lines changed: 76 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,76 @@
1+
Use `softioc.autosave` in an IOC
2+
================================
3+
4+
`../tutorials/creating-an-ioc` shows how to create a pythonSoftIOC.
5+
6+
7+
Example IOC
8+
-----------
9+
10+
.. literalinclude:: ../examples/example_autosave_ioc.py
11+
12+
Records are instantiated as normal and configured for automatic loading and
13+
periodic saving to a backup file with use of the keyword argument ``autosave``.
14+
``autosave`` resolves to a list of strings, which are the names of fields to be
15+
tracked by autosave. By default ``autosave=False``, which disables autosave for that PV.
16+
Setting ``autosave=True`` is equivalent to passing ``["VAL"]``. Note that ``"VAL"`` must be
17+
explicitly passed when tracking other fields, e.g. ``["VAL", "LOPR", "HOPR"]``.
18+
``autosave`` can also accept a single string field name as an argument.
19+
20+
The field values get written into a yaml-formatted file containing key-value pairs.
21+
By default the keys are the same as the full PV name, including any device name specified
22+
in :func:`~softioc.builder.SetDeviceName()`.
23+
24+
Autosave is disabled until :func:`~softioc.autosave.configure()` is called. The first two arguments,
25+
``directory`` and ``name`` are required. Backup files are periodically written into
26+
``directory`` with the name ``<name>.softsav`` every ``save_period`` seconds,
27+
set to 30.0 by default. The directory must exist, and should be configured with the appropriate
28+
read/write permissions for the user running the IOC.
29+
30+
IOC developers should only need to interface with autosave via the :func:`~softioc.autosave.configure()`
31+
method and the ``autosave`` keyword argument. Alternatively,
32+
PVs can be instantiated inside the :class:`~softioc.autosave.Autosave()` context manager, which
33+
automatically passes the ``autosave`` argument to any PVs created
34+
inside the context manager. If any fields are already specified by the ``autosave`` keyword
35+
argument of a PV's initialisation call the lists of fields to track get combined.
36+
All other module members are intended for internal use only.
37+
38+
In normal operation, loading from a backup is performed once during the
39+
:func:`~softioc.builder.LoadDatabase()` call and periodic saving to the backup file begins when
40+
:func:`~softioc.softioc.iocInit()` is called, provided that any PVs are configured to be saved.
41+
Currently, manual loading from a backup at runtime after ioc initialisation is not supported.
42+
Saving only occurs when any of the saved field values have changed since the last save.
43+
Users are discouraged from manually editing the backup files while the
44+
IOC is running so that the internal state of the autosave thread is consistent with
45+
the backup file.
46+
47+
If autosave is enabled and active, a timestamped copy of the latest existing autosave backup file is created
48+
when the IOC is restarted, e.g. ``<name>.softsav_240717-095004`` (timestamps are in the format yymmdd-HHMMSS).
49+
If you only wish to store one backup of the autosave file at a time, ``timestamped_backups=False``
50+
can be passed to :func:`~softioc.autosave.configure()` when it is called, this will create a backup file
51+
named ``<name>.softsav.bu``. To disable any autosaving, comment out the
52+
:func:`~softioc.autosave.configure()` call or pass it the keyword argument
53+
``enabled=False``.
54+
55+
The resulting backup file after running the example IOC for about 30 seconds is the following:
56+
57+
.. code-block::
58+
59+
MY-DEVICE-PREFIX:AI.EGU: ''
60+
MY-DEVICE-PREFIX:AI.PREC: '0'
61+
MY-DEVICE-PREFIX:AO: 0.0
62+
MY-DEVICE-PREFIX:AUTOMATIC-AO: 0.0
63+
MY-DEVICE-PREFIX:AUTOMATIC-AO.EGU: ''
64+
MY-DEVICE-PREFIX:AUTOMATIC-AO.HOPR: '0'
65+
MY-DEVICE-PREFIX:AUTOMATIC-AO.LOPR: '0'
66+
MY-DEVICE-PREFIX:SECONDSRUN: 29
67+
MY-DEVICE-PREFIX:WAVEFORMIN: [0, 0, 0, 0]
68+
69+
70+
If the IOC is stopped and restarted, the SECONDSRUN record will load its saved
71+
value of 29 from the backup.
72+
All non-VAL fields are stored as strings. Waveform type records holding arrays
73+
are cast into lists before saving.
74+
75+
This example IOC uses cothread, but autosave works identically when using
76+
an asyncio dispatcher.

docs/index.rst

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -59,6 +59,7 @@ Table Of Contents
5959
:maxdepth: 1
6060

6161
how-to/use-asyncio-in-an-ioc
62+
how-to/use-autosave-in-an-ioc
6263
how-to/make-publishable-ioc
6364
how-to/read-data-from-ioc
6465
how-to/use-soft-records

0 commit comments

Comments
 (0)