ifw-daq  3.0.0-pre2
IFW Data Acquisition modules
simulator.py
Go to the documentation of this file.
1 """
2 @file
3 @ingroup daq_recifsim
4 @copyright 2022 ESO - European Southern Observatory
5 
6 @brief Implements simulation of recdaqif
7 """
8 # pylint: skip-file
9 import inspect
10 import json
11 import logging
12 import os
13 import time
14 
15 import numpy as np
16 from astropy.io import fits
17 from ModDaqsimif.Daqsimif import Error as SimCtlError
18 from ModRecif.Recif import (ExceptionErr, RecProperties, RecStatus,
19  RecStatusNames, RecWaitSpec, RecWaitStatus,
20  RecWaitStatusNames)
21 from ModRecif.Recif.RecCmds import RecCmdsSyncService
22 
23 
24 class Recording:
25  """Holds simulated status for recording"""
26 
27  def __init__(self, id):
28  self.idid = id
29  self.dp_filesdp_files = []
30  self.files_generatedfiles_generated = 0
31  self.start_timestart_time = 1.0
32  self.end_timeend_time = 1.0
33  self.time_elapsedtime_elapsed = 0.0
34  self.remaining_timeremaining_time = 1.0
35  self.frames_processedframes_processed = 0
36  self.frames_remainingframes_remaining = 1
37  self.size_recordedsize_recorded = 0
38  self.infoinfo = ""
39  self.statusstatus = RecStatusNames.Undefined
40 
41 
42 class Simulator:
43  """Simulator that implements RecCmdsSyncService and sets up
44  simulation behaviour for Simulator.
45 
46  Registered hooks are run once and then removed before executed.
47  Each hook is a callable that takes the result from the default implementation
48  as well as the arguments given to the original implementation.
49  The hook can then choose to modify the existing result or replace it.
50  """
51 
52  def __init__(self, name, mal, dataroot):
53  self.namename = name
54  self.malmal = mal
55  self.datarootdataroot = dataroot
56  self.current_reccurrent_rec = None
57  self.recordingsrecordings = {} # id: status
58  # Oneshot commands
59  self.sim_hookssim_hooks = {}
60 
61  def RecStart(self, properties: RecProperties) -> RecStatus:
62  """@implements RecCmds.StartDaq"""
63  rec_id = properties.getId()
64  logging.info("Request: StartDaq(%s)", rec_id)
65  if rec_id in self.recordingsrecordings:
66  raise ExceptionErr("Recording with id already exist: %s" % rec_id, 1234)
67  self.current_reccurrent_rec = rec = Recording(rec_id)
68  logging.info("Request: RecStart(%s) ref=%s", rec_id, id(rec))
69  rec.status = RecStatusNames.Active
70  self.recordingsrecordings[rec_id] = rec
71  result = self.make_rec_statusmake_rec_status(rec_id)
72  return self.run_sim_hookrun_sim_hook(result, rec_id)
73 
74  def RecStop(self) -> RecStatus:
75  """@implements RecCmds.RecStop"""
76  try:
77  if not self.current_reccurrent_rec:
78  raise ExceptionErr("No current recording active", 1234)
79  rec_id = self.current_reccurrent_rec.id
80  logging.info("Request: RecStop(%s)", rec_id)
81 
82  rec = self.get_recordingget_recording(rec_id)
83 
84  if rec.status in (RecStatusNames.Completed, RecStatusNames.Stopped):
85  logging.info(
86  "Request: RecStop(%s): Recording already complete. Returning error",
87  rec_id,
88  )
89  raise ExceptionErr("Recording already stopped '%s'" % rec_id, 1234)
90 
91  path = os.path.join(self.datarootdataroot, "%s_%s.fits" % (rec_id, self.namename))
92  start = time.monotonic()
93  hdul = fits.HDUList([fits.PrimaryHDU()])
94  hdul.append(fits.ImageHDU(np.ones((100, 100)), name=self.namename))
95  hdul.writeto(path)
96  stop = time.monotonic()
97  logging.info(
98  "Request: RecStop(%s): FITS written to %s (took %.1fs)",
99  rec_id,
100  path,
101  stop - start,
102  )
103 
104  rec.dp_files = [path]
105  rec.status = RecStatusNames.Stopped
106 
107  result = self.make_rec_statusmake_rec_status(rec_id)
108  # Due to MAL bug we have to continue keeping the current recording alive...
109  # self.current_rec = None
110  return self.run_sim_hookrun_sim_hook(result, rec_id)
111  except Exception as e:
112  logging.exception("RecStop failed")
113  raise ExceptionErr("RecStop failed: {str(e)}", 1234)
114 
115  def RecAbort(self) -> str:
116  """@implements RecCmds.RecAbort"""
117  if not self.current_reccurrent_rec:
118  raise ExceptionErr("No current recording active", 1234)
119 
120  try:
121  rec_id = self.current_reccurrent_rec.id
122  logging.info("Request: RecAbort(%s)", rec_id)
123  rec = self.get_recordingget_recording(rec_id)
124  rec.status = RecStatusNames.Aborted
125  self.recordingsrecordings[rec_id] = rec
126  result = ""
127  self.current_reccurrent_rec = None
128  return self.run_sim_hookrun_sim_hook(result, rec_id)
129  except Exception as e:
130  logging.exception("RecAbort faied")
131  raise ExceptionErr("RecAbort failed: {str(e)}", 1234)
132  except:
133  logging.exception("RecAbort faied with unknown error")
134  raise ExceptionErr("RecAbort failed with unknown error", 1234)
135 
136  def RecWait(
137  self, spec: RecWaitSpec
138  ) -> RecStatus:
139  """@implements RecCmds.RecWait
140 
141  For the moment we simply simulate that the recording was completed
142  as soon as this request was received.
143  """
144  rec_id = self.current_reccurrent_rec.id
145  logging.info("Request: RecWait(%s)", rec_id)
146  result = self.make_rec_wait_statusmake_rec_wait_status(rec_id)
147  return self.run_sim_hookrun_sim_hook(result, rec_id)
148 
149  def RecStatus(self, rec_id: str) -> RecStatus:
150  """@implements RecCmds.RecStatus"""
151  logging.info("Request: RecStatus(%s)", rec_id)
152  result = self.make_rec_statusmake_rec_status(rec_id)
153  return self.run_sim_hookrun_sim_hook(result, rec_id)
154 
155  def RecPause(self) -> RecStatus:
156  """@implements RecCmds.RecPause"""
157  logging.info("Request: RecPause(): Throwing not implemented error")
158  raise ExceptionErr("Not implemented", 1234)
159 
160  def get_recording(self, rec_id) -> Recording:
161  """Get recording or raise ExceptionErr."""
162  if rec_id in self.recordingsrecordings:
163  return self.recordingsrecordings[rec_id]
164  raise ExceptionErr("Unknown recording id '%s'" % rec_id, 1234)
165 
166  def make_rec_status(self, rec_id) -> RecStatus:
167  """
168  <struct name="RecStatus">
169  <member name="dpFiles" type="string" arrayDimensions="(32)"/>
170  <member name="endTime" type="double"/>
171  <member name="filesGenerated" type="int32_t"/>
172  <member name="framesProcessed" type="int32_t"/>
173  <member name="framesRemaining" type="int32_t"/>
174  <member name="id" type="string"/>
175  <member name="info" type="string"/>
176  <member name="remainingTime" type="double"/>
177  <member name="sizeRecorded" type="int32_t"/>
178  <member name="startTime" type="double"/>
179  <member name="status" type="nonBasic" nonBasicTypeName="RecStatusNames"/>
180  <member name="timeElapsed" type="double"/>
181  </struct>
182  """
183  logging.debug("Creating return type: RecStatus for id %s", rec_id)
184  rec = self.get_recordingget_recording(rec_id)
185  status = self.malmal.createDataEntity(RecStatus)
186  self.populate_rec_statuspopulate_rec_status(rec, status)
187  return status
188 
189  def populate_rec_status(self, rec: Recording, status: RecStatus) -> RecStatus:
190  """Populates RecStatus from status"""
191  logging.debug(
192  "Populating return type: RecStatus: id=%s, status=%r (ref=%s)",
193  rec.id,
194  rec.status,
195  id(rec),
196  )
197  status.setDpFiles(rec.dp_files)
198  status.setEndTime(rec.end_time)
199  status.setFilesGenerated(rec.files_generated)
200  status.setFramesProcessed(rec.frames_processed)
201  status.setFramesRemaining(rec.frames_remaining)
202  status.setId(rec.id)
203  status.setInfo(rec.info)
204  status.setRemainingTime(rec.remaining_time)
205  status.setSizeRecorded(rec.size_recorded)
206  status.setStartTime(rec.start_time)
207  status.setStatus(rec.status)
208  status.setTimeElapsed(rec.time_elapsed)
209 
210  def make_rec_wait_status(self, rec_id) -> RecWaitStatus:
211  """Creates RecWaitStatus and populates attributes"""
212  logging.debug("Creating return type: RecWaitStatus")
213  rec = self.get_recordingget_recording(rec_id)
214  status = self.malmal.createDataEntity(RecWaitStatus)
215  self.populate_rec_statuspopulate_rec_status(rec, status.getRecStatus())
216  if rec.status in (RecStatusNames.Completed, RecStatusNames.Stopped):
217  logging.debug(
218  "Recording is Completed or Stopped, RecWaitStatus set to success"
219  )
220  status.setStatus(RecWaitStatusNames.Success)
221  else:
222  status.setStatus(RecWaitStatusNames.Timeout)
223  return status
224 
225  def add_sim_hook(self, cmd_name: str, hook):
226  """Add the simulation hook `hook` for the specified command `cmd_name` (e.g.
227  "RecStart").
228 
229  Any previous hooks will be replaced.
230  """
231  self.sim_hookssim_hooks[cmd_name] = hook
232 
233  def reset(self):
234  """Reset simulator to initial default state."""
235  self.clear_sim_hooksclear_sim_hooks()
236  self.recordingsrecordings = {}
237  self.current_reccurrent_rec = None
238 
239  def clear_sim_hooks(self):
240  """Remove all simulation hooks."""
241  self.sim_hookssim_hooks = {}
242 
243  def run_sim_hook(self, result, *args):
244  """Runs simulation hook that may modify or replace the default implementation.
245 
246  The hook to execute is automatically determined from the call stack, so
247  this should only ever be executed directly from the service command
248  implementation method.
249  """
250  cmd_name = inspect.stack()[1][3]
251  hook = self.sim_hookssim_hooks.get(cmd_name)
252  logging.debug("Checking for hook for command %s", cmd_name)
253  if hook:
254  logging.debug("Running simulation hook for command %s", cmd_name)
255  del self.sim_hookssim_hooks[cmd_name]
256  return hook(result, *args)
257  else:
258  logging.debug(
259  "No hook for %s, returning %s (%s)", cmd_name, result, type(result)
260  )
261  return result
262 
263 
264 class SimulatorCtl:
265  """Simulator controller that implements ModDaqsimif and sets up
266  simulation behaviour for Simulator.
267  """
268 
269  def __init__(self, name, mal, server, dataroot):
270  self.namename = name
271  self.malmal = mal
272  self.serverserver = server
273  self.datarootdataroot = dataroot
274 
275  self.simsim = Simulator(name, mal, dataroot)
276  logging.info("Registering service 'rec'")
277  self.serverserver.registerService("rec", RecCmdsSyncService, self.simsim)
278 
279  def Setup(self, spec):
280  """@implements SimCtl.Setup."""
281  logging.info('SimCtl.Setup: spec="%s"', spec)
282  s = json.loads(spec)
283  try:
284  cmd = s.get("command", "")
285  action = s.get("action", "")
286  if action == "complete-recording":
287  # finish current recording
288  rec = self.simsim.current_rec
289  logging.info(
290  "SimCtl.Setup: Marking %s as Completed ref=%s", rec.id, id(rec)
291  )
292  rec.status = RecStatusNames.Completed
293  path = os.path.join(
294  self.datarootdataroot, "auto_%s_%s.fits" % (rec.id, self.namename)
295  )
296 
297  hdul = fits.HDUList([fits.PrimaryHDU()])
298  hdul.append(fits.ImageHDU(np.ones((100, 100)), name=self.namename))
299  hdul.writeto(path)
300  logging.info("SimCtl.Setup: %s FITS written to %s", rec.id, path)
301  rec.dp_files = [path]
302  assert id(self.simsim.get_recording(rec.id)) == id(rec)
303 
304  elif action == "throw":
305 
306  def throw(res, *args):
307  logging.debug("Throwing exception")
308  raise ExceptionErr(
309  "Simulated error (current id: %s)"
310  % getattr(self.simsim.current_rec, "id", "n/a"),
311  1234,
312  )
313 
314  self.simsim.add_sim_hook(cmd, throw)
315  else:
316  raise SimCtlError("Unknown action '%s' provided" % action)
317  logging.info("SimCtl.Setup: Returning empty string")
318  return json.dumps("")
319  except SimCtlError:
320  logging.exception("SimCtl.Setup: Failed")
321  raise
322  except Exception as e:
323  logging.exception("SimCtl.Setup: Failed")
324  raise SimCtlError('"%s"' % str(e))
325 
326  def Reset(self):
327  """@implements SimCtl.Reset"""
328  try:
329  self.simsim.reset()
330  except Exception as e:
331  raise SimCtlError(str(e))
332 
333  def ForceExit(self):
334  """@implements SimCtl.ForceExit."""
335  # Notify main thread that it should exit
336  logging.warning("Force Exiting without cleanup!")
337  os._exit()
Holds simulated status for recording.
Definition: simulator.py:25
def __init__(self, id)
Definition: simulator.py:27
Simulator controller that implements ModDaqsimif and sets up simulation behaviour for Simulator.
Definition: simulator.py:267
def __init__(self, name, mal, server, dataroot)
Definition: simulator.py:269
def Setup(self, spec)
Definition: simulator.py:280
Simulator that implements RecCmdsSyncService and sets up simulation behaviour for Simulator.
Definition: simulator.py:50
Recording get_recording(self, rec_id)
Get recording or raise ExceptionErr.
Definition: simulator.py:161
RecStatus RecStatus(self, str rec_id)
Definition: simulator.py:150
RecStatus populate_rec_status(self, Recording rec, RecStatus status)
Populates RecStatus from status.
Definition: simulator.py:190
RecStatus RecPause(self)
Definition: simulator.py:156
def add_sim_hook(self, str cmd_name, hook)
Add the simulation hook hook for the specified command cmd_name (e.g.
Definition: simulator.py:230
RecStatus RecStart(self, RecProperties properties)
Definition: simulator.py:62
RecStatus make_rec_status(self, rec_id)
<struct name="RecStatus"> <member name="dpFiles" type="string" arrayDimensions="(32)"> <member name="...
Definition: simulator.py:182
RecStatus RecWait(self, RecWaitSpec spec)
Definition: simulator.py:143
RecWaitStatus make_rec_wait_status(self, rec_id)
Creates RecWaitStatus and populates attributes.
Definition: simulator.py:211
def reset(self)
Reset simulator to initial default state.
Definition: simulator.py:234
def run_sim_hook(self, result, *args)
Runs simulation hook that may modify or replace the default implementation.
Definition: simulator.py:249
RecStatus RecStop(self)
Definition: simulator.py:75
def clear_sim_hooks(self)
Remove all simulation hooks.
Definition: simulator.py:240
def __init__(self, name, mal, dataroot)
Definition: simulator.py:52