Skip to content

Commit

Permalink
Add support for autosaving non-VAL pv fields
Browse files Browse the repository at this point in the history
  • Loading branch information
jsouter committed Jul 9, 2024
1 parent 0017423 commit 241e872
Show file tree
Hide file tree
Showing 4 changed files with 62 additions and 31 deletions.
78 changes: 51 additions & 27 deletions softioc/autosave.py
Original file line number Diff line number Diff line change
@@ -1,12 +1,13 @@
import yaml
import atexit
import shutil
import sys
import threading
import traceback
from datetime import datetime
from pathlib import Path

import yaml
from numpy import ndarray
from softioc.device_core import LookupRecordList
import sys
import atexit

SAV_SUFFIX = "softsav"
SAVB_SUFFIX = "softsavB"
Expand Down Expand Up @@ -61,6 +62,20 @@ def _shutdown_autosave_thread(autosaver, worker):
worker.join()


def add_pv_to_autosave(pv, name, field=None):
Autosave._pvs[name] = AutosavePV(pv, field)


class AutosavePV:
def __init__(self, pv, field = None):
if not field or field == "VAL":
self.get = pv.get
self.set = lambda val: pv.set(val)
else:
self.get = lambda: pv.get_field(field)
self.set = lambda val: pv.set_field(field, val)


class Autosave:
_pvs = {}
_last_saved_state = {}
Expand Down Expand Up @@ -95,16 +110,16 @@ def __init__(self):
self._last_saved_time = datetime.now()
if self.backup_on_restart:
self._backup_sav_file()
self._pvs = {name: pv for name, pv in LookupRecordList() if pv.autosave}

def _backup_sav_file(self):
sav_path = self._get_current_sav_path()
if sav_path.is_file():
shutil.copy2(sav_path, self._get_timestamped_backup_sav_path())
else:
sys.stderr.write(
f"Could not back up autosave, {sav_path} is not a file"
f"Could not back up autosave, {sav_path} is not a file\n"
)
sys.stderr.flush()

def _get_timestamped_backup_sav_path(self):
sav_path = self._get_current_sav_path()
Expand All @@ -118,9 +133,31 @@ def _get_backup_save_path(self):
def _get_current_sav_path(self):
return self.directory / f"{self.device_name}.{SAV_SUFFIX}"

def _get_state(self):
# state = {pv_field: pv.get() for pv_field, pv in self._pvs.items()}
state = {}
for pv_field, pv in self._pvs.items():
try:
state[pv_field] = pv.get()
except Exception as e:
sys.stderr.write("Exception getting {pv_field}: {e}\n")
sys.stderr.flush()
return state

def _set_pvs_from_saved_state(self):
for pv_field, value in self._last_saved_state.items():
try:
pv = self._pvs[pv_field]
pv.set(value)
except Exception as e:
sys.stderr.write(
f"Exception setting {pv_field} to {value}: {e}\n"
)
sys.stderr.flush()

def _save(self):
try:
state = {name: pv.get() for name, pv in self._pvs.items()}
state = self._get_state()
if state != self._last_saved_state:
for path in [
self._get_current_sav_path(),
Expand All @@ -131,40 +168,27 @@ def _save(self):
self._last_saved_state = state
self._last_saved_time = datetime.now()
except Exception as e:
sys.stderr.write(f"Could not save state to file: {e}")
sys.stderr.write(f"Could not save state to file: {e}\n")
sys.stderr.flush()

def _load(self, path=None):
if not self.enabled:
sys.stdout.write(
"Not loading from file as autosave adapter disabled"
)
return
sav_path = path or self._get_current_sav_path()
if not sav_path or not sav_path.is_file():
sys.stderr.write(
f"Could not load autosave values from file {sav_path}"
f"Could not load autosave values from file {sav_path}\n"
)
sys.stderr.flush()
return
with open(sav_path, "r") as f:
self._last_saved_state = yaml.full_load(f)
for name, value in self._last_saved_state.items():
try:
pv = self._pvs.get(name)
pv.set(value)
except Exception as e:
sys.stderr.write(f"Exception setting {name} to {value}: {e}")
self._set_pvs_from_saved_state()

def stop(self):
self._stop_event.set()

def loop(self):
if not self.enabled or not self._pvs:
return
# wait until iocInit has been called
# TODO: put in a timeout here otherwise this may get silently stuck...
while True:
if all(hasattr(pv, "_record") for pv in self._pvs.values()):
break
self._load() # load at startup if enabled
while True:
try:
Expand All @@ -173,5 +197,5 @@ def loop(self):
return
else: # No stop requested, we should save and continue
self._save()
except Exception as e:
sys.stderr.write(f"Exception in autosave loop: {e}")
except Exception:
traceback.print_exc()
9 changes: 7 additions & 2 deletions softioc/device.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
import ctypes
from ctypes import *
import numpy

from .autosave import add_pv_to_autosave
from . import alarm
from . import fields
from .imports import (
Expand Down Expand Up @@ -55,7 +55,12 @@ class ProcessDeviceSupportCore(DeviceSupportCore, RecordLookup):

# all record types can support autosave
def __init__(self, name, **kargs):
self.autosave = kargs.pop("autosave", False)
autosave = kargs.pop("autosave", False)
if autosave:
add_pv_to_autosave(self, name)
autosave_fields = kargs.pop("autosave_fields", [])
for field in autosave_fields:
add_pv_to_autosave(self, f"{name}.{field}", field)
self.__super.__init__(name, **kargs)

# Most subclasses (all except waveforms) define a ctypes constructor for the
Expand Down
4 changes: 3 additions & 1 deletion softioc/pythonSoftIoc.py
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,9 @@ def __init__(self, builder, device, name, **fields):
# have to maintain this separately from the corresponding device list.
DeviceKeywords = [
'on_update', 'on_update_name', 'validate', 'always_update',
'initial_value', '_wf_nelm', '_wf_dtype', 'blocking', 'autosave']
'initial_value', '_wf_nelm', '_wf_dtype', 'blocking',
'autosave', 'autosave_fields'
]
device_kargs = {}
for keyword in DeviceKeywords:
if keyword in fields:
Expand Down
2 changes: 1 addition & 1 deletion softioc/softioc.py
Original file line number Diff line number Diff line change
Expand Up @@ -33,8 +33,8 @@ def iocInit(dispatcher=None):
dispatcher = cothread_dispatcher.CothreadDispatcher()
# Set the dispatcher for record processing callbacks
device.dispatcher = dispatcher
autosave.start_autosave_thread()
imports.iocInit()
autosave.start_autosave_thread()


def safeEpicsExit(code=0):
Expand Down

0 comments on commit 241e872

Please sign in to comment.