Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Autosave support #163

Merged
merged 53 commits into from
Sep 13, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
53 commits
Select commit Hold shift + click to select a range
e7f5aa5
Add initial outline for Autosave class
jsouter Jul 1, 2024
41ca3cd
get autosave device name from builder
jsouter Jul 2, 2024
c88dc29
Start autosave thread when in dispatcher init
jsouter Jul 3, 2024
a7a1084
remove autosave._state member
jsouter Jul 3, 2024
db187c4
Remove poll_period from autosave, alter autosave.configure logic
jsouter Jul 3, 2024
6180e55
call stop on autosave thread at dispatcher exit
jsouter Jul 3, 2024
5e06029
Fix autosave loop logic, allow autosave to be set after pv init but b…
jsouter Jul 4, 2024
4359357
Add load_req_file util function
jsouter Jul 4, 2024
4760487
represent ndarrays as list in yaml to support waveform autosave
jsouter Jul 4, 2024
760bb1a
linting
jsouter Jul 4, 2024
0a7e2d3
move autosave thread start to iocInit.
jsouter Jul 5, 2024
dcfb4d7
remove public methods for adding PVs to Autosave after Autosave init …
jsouter Jul 8, 2024
9651a57
Make autosave methods private, set to disabled by default if configur…
jsouter Jul 8, 2024
fd586bd
add docstring to autosave.configure
jsouter Jul 8, 2024
b84eee4
remove unused _change_directory method
jsouter Jul 8, 2024
c7ad3f6
Move autosave thread functions to autosave.py, fix try block logic
jsouter Jul 8, 2024
57cfb30
block autosave thread until iocInit called
jsouter Jul 8, 2024
793616a
Add support for autosaving non-VAL pv fields
jsouter Jul 9, 2024
6bea3fd
Add autosave units tests
jsouter Jul 10, 2024
816c158
Support loading of fields for In records by requiring explicit autosa…
jsouter Jul 11, 2024
74d6cc9
require autosave file name to be passed explicitly
jsouter Jul 12, 2024
997266a
check for equality between arrays when loading from autosave
jsouter Jul 12, 2024
a8e9a04
Remove device prefix from autosave signal name by default
jsouter Jul 12, 2024
f3b181e
Fix tests for autosave api changes
jsouter Jul 12, 2024
377602a
write autosave state to backup file then rename, add configuration op…
jsouter Jul 16, 2024
f7d0980
Add docstring and rename variables for autosave
jsouter Jul 16, 2024
3de2b86
test load works for all autosave types
jsouter Jul 17, 2024
33a03bd
Add full-system IOC save and load tests and autosave how-to doc
jsouter Jul 18, 2024
500bed7
Remove ability to override autosave backup key for record
jsouter Jul 18, 2024
3c87241
Add autosave documentation to api.rst, set save period to default dir…
jsouter Jul 23, 2024
cdcb4e9
add autosave to CHANGELOG.rst
jsouter Jul 25, 2024
2f31838
Allow autosave backups to be overwritten without timestamps
jsouter Aug 7, 2024
2b72c41
only use device prefix in full system autosave tests to prevent break…
jsouter Aug 12, 2024
facfd21
Rollback change to strip device prefix from autosave key
jsouter Aug 21, 2024
a4cffad
Allow autosave.Autosave to be called as a context manager
jsouter Aug 21, 2024
38d02f9
Make all Autosave methods class methods
jsouter Aug 22, 2024
3b17497
update docs for autosave api changes
jsouter Aug 22, 2024
75a1e2d
implement changes from review comments
jsouter Aug 30, 2024
afb7947
Check if autosave directory exists on configure call
jsouter Sep 2, 2024
773c0a2
Simplify context manager logic when adding pv to autosave
jsouter Sep 2, 2024
f4db4f2
Move some configuration attributes and methods out of Autosave class
jsouter Sep 4, 2024
c99dfb6
Convert Autosave into threading.local singleton to make context manag…
jsouter Sep 4, 2024
847b825
Move singleton threading logic to _AutosaveContext class
jsouter Sep 4, 2024
e4b950d
Fix docs rendering and add seealso links to PV kwargs to autosave API…
jsouter Sep 5, 2024
f4d3ec7
Use single autosave kwarg instead of autosave and autosave_fields
jsouter Sep 9, 2024
22c7194
Bump github actions versions to v4
jsouter Sep 9, 2024
49fb971
Add test for interaction between autosave kwarg and context manager
jsouter Sep 10, 2024
eac52e1
Add test case for single string autosave kwarg
jsouter Sep 10, 2024
6341098
setting autosave=False in PV overrides context manager
jsouter Sep 10, 2024
0d00d75
add pyyaml dependency to setup.py
jsouter Sep 11, 2024
4822ac8
use threading.Event to remove timing element from autosave context ma…
jsouter Sep 11, 2024
a2e2ccf
CI fixes
jsouter Sep 11, 2024
8b6413b
CI fix for macos python3.7
jsouter Sep 13, 2024
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
38 changes: 24 additions & 14 deletions .github/workflows/code.yml
Original file line number Diff line number Diff line change
Expand Up @@ -17,10 +17,10 @@ jobs:
runs-on: "ubuntu-latest"
steps:
- name: Checkout Source
uses: actions/checkout@v2
uses: actions/checkout@v4

- name: Install Python
uses: actions/setup-python@v2
uses: actions/setup-python@v4
with:
python-version: "3.7"

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

- name: Upload Sdist
uses: actions/upload-artifact@v2
uses: actions/upload-artifact@v4
with:
name: dist
path: dist/*
Expand All @@ -56,6 +56,11 @@ jobs:
os: [ubuntu-latest, windows-latest, macos-latest]
python: [cp37, cp38, cp39, cp310]

exclude:
# MacOS 14.4.1 for arm64 doesn't support Python < 3.8
- os: macos-latest
python: "cp37"

include:
# Put coverage and results files in the project directory for mac
- os: macos-latest
Expand All @@ -69,22 +74,27 @@ jobs:
- os: ubuntu-latest
cov_file: /output/coverage.xml
results_file: /output/pytest-results.xml
# MacOS 13 required for Python < 3.8
- os: macos-13
python: "cp37"
cov_file: "{project}/dist/coverage.xml"
results_file: "{project}/dist/pytest-results.xml"

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

steps:
- name: Checkout Source
uses: actions/checkout@v2
uses: actions/checkout@v4
with:
# require history to get back to last tag for version number of branches
fetch-depth: 0
submodules: true

- name: Install Python
uses: actions/setup-python@v2
uses: actions/setup-python@v4
with:
python-version: "3.7"
python-version: "3.12"

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

- name: Upload Wheel
uses: actions/upload-artifact@v2
uses: actions/upload-artifact@v4
with:
name: dist
name: dist-${{ matrix.os }}-${{ matrix.python }}
path: dist/softioc*

- name: Upload coverage to Codecov
uses: codecov/codecov-action@v2
uses: codecov/codecov-action@v4
with:
name: ${{ matrix.os }}/${{ matrix.python }}
directory: dist

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

steps:
- name: Download Artifacts
uses: actions/download-artifact@v2
uses: actions/download-artifact@v4
with:
path: artifacts

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

steps:
- uses: actions/download-artifact@v2
- uses: actions/download-artifact@v4
with:
name: dist
path: dist
Expand All @@ -167,7 +177,7 @@ jobs:
# upload to PyPI and make a release on every tag
if: github.event_name == 'push' && startsWith(github.event.ref, 'refs/tags')
steps:
- uses: actions/download-artifact@v2
- uses: actions/download-artifact@v4
with:
name: dist
path: dist
Expand Down
2 changes: 1 addition & 1 deletion CHANGELOG.rst
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ Unreleased_
-----------

Added:

- `Add autosave support to all records and record fields <../../pull/163>`_
- `Add int64In/Out record support <../../pull/161>`_
- `Enable setting alarm status of Out records <../../pull/157>`_
- `Adding the non_interactive_ioc function <../../pull/156>`_
Expand Down
90 changes: 90 additions & 0 deletions Pipfile.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

34 changes: 34 additions & 0 deletions docs/examples/example_autosave_ioc.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
from softioc import autosave, builder, softioc
import cothread

# Set the record prefix
builder.SetDeviceName("MY-DEVICE-PREFIX")

# Create records, set some of them to autosave, also save some of their fields

builder.aOut("AO", autosave=True)
builder.aIn("AI", autosave=["PREC", "EGU"])
builder.boolIn("BO")
builder.WaveformIn("WAVEFORMIN", [0, 0, 0, 0], autosave=True)
with autosave.Autosave(["VAL", "LOPR", "HOPR"]):
builder.aOut("AUTOMATIC-AO", autosave=["EGU"])
seconds = builder.longOut("SECONDSRUN", autosave=True)

autosave.configure(
directory="/tmp/autosave-data",
name="MY-DEVICE-PREFIX",
save_period=5.0
)

builder.LoadDatabase()
softioc.iocInit()

# Start processes required to be run after iocInit
def update():
while True:
cothread.Sleep(1)
seconds.set(seconds.get() + 1)

cothread.Spawn(update)

softioc.interactive_ioc(globals())
76 changes: 76 additions & 0 deletions docs/how-to/use-autosave-in-an-ioc.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
Use `softioc.autosave` in an IOC
jsouter marked this conversation as resolved.
Show resolved Hide resolved
================================

`../tutorials/creating-an-ioc` shows how to create a pythonSoftIOC.


Example IOC
-----------

.. literalinclude:: ../examples/example_autosave_ioc.py

Records are instantiated as normal and configured for automatic loading and
periodic saving to a backup file with use of the keyword argument ``autosave``.
``autosave`` resolves to a list of strings, which are the names of fields to be
tracked by autosave. By default ``autosave=False``, which disables autosave for that PV.
Setting ``autosave=True`` is equivalent to passing ``["VAL"]``. Note that ``"VAL"`` must be
explicitly passed when tracking other fields, e.g. ``["VAL", "LOPR", "HOPR"]``.
``autosave`` can also accept a single string field name as an argument.

The field values get written into a yaml-formatted file containing key-value pairs.
By default the keys are the same as the full PV name, including any device name specified
in :func:`~softioc.builder.SetDeviceName()`.

Autosave is disabled until :func:`~softioc.autosave.configure()` is called. The first two arguments,
``directory`` and ``name`` are required. Backup files are periodically written into
``directory`` with the name ``<name>.softsav`` every ``save_period`` seconds,
set to 30.0 by default. The directory must exist, and should be configured with the appropriate
read/write permissions for the user running the IOC.

IOC developers should only need to interface with autosave via the :func:`~softioc.autosave.configure()`
method and the ``autosave`` keyword argument. Alternatively,
PVs can be instantiated inside the :class:`~softioc.autosave.Autosave()` context manager, which
automatically passes the ``autosave`` argument to any PVs created
inside the context manager. If any fields are already specified by the ``autosave`` keyword
argument of a PV's initialisation call the lists of fields to track get combined.
All other module members are intended for internal use only.

In normal operation, loading from a backup is performed once during the
:func:`~softioc.builder.LoadDatabase()` call and periodic saving to the backup file begins when
:func:`~softioc.softioc.iocInit()` is called, provided that any PVs are configured to be saved.
Currently, manual loading from a backup at runtime after ioc initialisation is not supported.
Saving only occurs when any of the saved field values have changed since the last save.
Users are discouraged from manually editing the backup files while the
IOC is running so that the internal state of the autosave thread is consistent with
the backup file.

If autosave is enabled and active, a timestamped copy of the latest existing autosave backup file is created
when the IOC is restarted, e.g. ``<name>.softsav_240717-095004`` (timestamps are in the format yymmdd-HHMMSS).
If you only wish to store one backup of the autosave file at a time, ``timestamped_backups=False``
can be passed to :func:`~softioc.autosave.configure()` when it is called, this will create a backup file
named ``<name>.softsav.bu``. To disable any autosaving, comment out the
:func:`~softioc.autosave.configure()` call or pass it the keyword argument
``enabled=False``.

The resulting backup file after running the example IOC for about 30 seconds is the following:

.. code-block::
MY-DEVICE-PREFIX:AI.EGU: ''
MY-DEVICE-PREFIX:AI.PREC: '0'
MY-DEVICE-PREFIX:AO: 0.0
MY-DEVICE-PREFIX:AUTOMATIC-AO: 0.0
MY-DEVICE-PREFIX:AUTOMATIC-AO.EGU: ''
MY-DEVICE-PREFIX:AUTOMATIC-AO.HOPR: '0'
MY-DEVICE-PREFIX:AUTOMATIC-AO.LOPR: '0'
MY-DEVICE-PREFIX:SECONDSRUN: 29
MY-DEVICE-PREFIX:WAVEFORMIN: [0, 0, 0, 0]
If the IOC is stopped and restarted, the SECONDSRUN record will load its saved
value of 29 from the backup.
All non-VAL fields are stored as strings. Waveform type records holding arrays
are cast into lists before saving.

This example IOC uses cothread, but autosave works identically when using
an asyncio dispatcher.
1 change: 1 addition & 0 deletions docs/index.rst
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,7 @@ Table Of Contents
:maxdepth: 1

how-to/use-asyncio-in-an-ioc
how-to/use-autosave-in-an-ioc
how-to/make-publishable-ioc
how-to/read-data-from-ioc
how-to/use-soft-records
Expand Down
Loading
Loading