1
1
import json
2
2
from pathlib import Path
3
3
from typing import Dict , List , Optional
4
- from softioc .pythonSoftIoc import RecordWrapper
5
4
from datetime import datetime
6
5
import shutil
7
- from softioc import builder
6
+ from softioc .device_core import LookupRecordList
7
+ import time
8
+ import threading
8
9
9
10
SAV_SUFFIX = "softsav"
10
11
SAVB_SUFFIX = "softsavB"
11
12
13
+ def configure (device = None , directory = None , save_period = None , poll_period = None ):
14
+ Autosave .poll_period = poll_period or Autosave .poll_period
15
+ Autosave .save_period = save_period or Autosave .save_period
16
+ if device is None and Autosave .device_name is None :
17
+ from .builder import GetRecordNames
18
+ Autosave .device_name = GetRecordNames ().prefix [0 ]
19
+ else :
20
+ Autosave .device_name = device
21
+ if directory is None and Autosave .directory is None :
22
+ raise RuntimeError ("Autosave directory is not known, "
23
+ "call autosave.configure() with directory keyword argument" )
24
+ else :
25
+ Autosave .directory = Path (directory )
12
26
13
27
class Autosave :
28
+ _instance = None
29
+ poll_period = 1.0
30
+ save_period = 30.0
31
+ device_name = None
32
+ directory = None
33
+ enabled = True
34
+ backup_on_restart = True
35
+
14
36
def __init__ (
15
37
self ,
16
- directory : str ,
17
- pvs : List [RecordWrapper ],
18
- save_period : float = 30.0 ,
19
- enabled : bool = True ,
20
- backup_on_restart : bool = True
21
38
):
22
- self ._device_name : str = builder .GetRecordNames ().prefix [0 ]
23
- self ._directory : Path = Path (directory ) # cast string to Path
39
+ if not self .directory :
40
+ raise RuntimeError ("Autosave directory is not known, "
41
+ "call autosave.configure() with directory keyword argument" )
42
+ if not self .device_name :
43
+ raise RuntimeError ("Device name is not known to autosave thread, "
44
+ "call autosave.configure() with device keyword argument" )
24
45
self ._last_saved_time = datetime .now ()
25
- if not self ._directory .is_dir ():
46
+ if not self .directory .is_dir ():
26
47
raise RuntimeError (f"{ directory } is not a valid autosave directory" )
27
- if backup_on_restart :
48
+ if self . backup_on_restart :
28
49
self .backup_sav_file ()
29
- self ._enabled = enabled
30
- self ._save_period = save_period
31
- self ._pvs = {pv .name : pv for pv in pvs }
32
- self ._state : Dict [str , RecordWrapper ] = {}
50
+ self .get_autosave_pvs ()
51
+ self ._state = {}
33
52
self ._last_saved_state = {}
53
+ self ._started = False
54
+ if self .enabled :
55
+ self .load () # load at startup if enabled
56
+
57
+ def get_autosave_pvs (self ):
58
+ self ._pvs = {name : pv for name , pv in LookupRecordList () if pv .autosave }
34
59
35
60
def _change_directory (self , directory : str ):
36
61
dir_path = Path (directory )
37
62
if dir_path .is_dir ():
38
- self ._directory = dir_path
63
+ self .directory = dir_path
39
64
else :
40
65
raise ValueError (f"{ directory } is not a valid autosave directory" )
41
66
@@ -44,27 +69,29 @@ def backup_sav_file(self):
44
69
if sav_path .is_file ():
45
70
shutil .copy2 (sav_path , self ._get_timestamped_backup_sav_path ())
46
71
47
- def add_pv (self , pv : RecordWrapper ):
72
+ def add_pv (self , pv ):
73
+ pv .autosave = True
48
74
self ._pvs [pv .name ] = pv
49
75
50
- def remove_pv (self , pv : RecordWrapper ):
76
+ def remove_pv (self , pv ):
77
+ pv .autosave = False
51
78
self ._pvs .pop (pv .name , None )
52
79
53
80
def _get_state_from_device (self ):
54
81
for name , pv in self ._pvs .items ():
55
82
self ._state [name ] = pv .get ()
56
83
57
- def _get_timestamped_backup_sav_path (self ) -> Path :
84
+ def _get_timestamped_backup_sav_path (self ):
58
85
sav_path = self ._get_current_sav_path ()
59
86
return sav_path .parent / (
60
87
sav_path .name + self ._last_saved_time .strftime ("_%y%m%d-%H%M%S" )
61
88
)
62
89
63
- def _get_backup_save_path (self ) -> Path :
64
- return self ._directory / f"{ self ._device_name } .{ SAVB_SUFFIX } "
90
+ def _get_backup_save_path (self ):
91
+ return self .directory / f"{ self .device_name } .{ SAVB_SUFFIX } "
65
92
66
- def _get_current_sav_path (self ) -> Path :
67
- return self ._directory / f"{ self ._device_name } .{ SAV_SUFFIX } "
93
+ def _get_current_sav_path (self ):
94
+ return self .directory / f"{ self .device_name } .{ SAV_SUFFIX } "
68
95
69
96
def _update_last_saved (self ):
70
97
self ._last_saved_state = self ._state .copy ()
@@ -80,19 +107,19 @@ def _save(self):
80
107
print (f"Could not save state to file: { e } " )
81
108
82
109
def save (self ):
83
- if not self ._enabled :
110
+ if not self .enabled :
84
111
print ("Not saving to file as autosave adapter disabled" )
85
112
return
86
113
timenow = datetime .now ()
87
114
self ._get_state_from_device ()
88
115
if (
89
- (timenow - self ._last_saved_time ).total_seconds () > self ._save_period
116
+ (timenow - self ._last_saved_time ).total_seconds () > self .save_period
90
117
and self ._state != self ._last_saved_state # only save if changed
91
118
):
92
119
self ._save ()
93
120
94
- def load (self , path : Optional [ str ] = None ):
95
- if not self ._enabled :
121
+ def load (self , path = None ):
122
+ if not self .enabled :
96
123
print ("Not loading from file as autosave adapter disabled" )
97
124
return
98
125
sav_path = path or self ._get_current_sav_path ()
@@ -108,3 +135,10 @@ def load(self, path: Optional[str] = None):
108
135
continue
109
136
pv .set (value )
110
137
self ._get_state_from_device ()
138
+
139
+ def loop (self ):
140
+ if not self ._pvs :
141
+ return # end thread if no PVs to save
142
+ while True :
143
+ time .sleep (self .poll_period )
144
+ self .save ()
0 commit comments