ifw-daq  1.0.0
IFW Data Acquisition modules
simulator.py
Go to the documentation of this file.
1 """
2 @file
3 @ingroup daq_sim_recif
4 @copyright 2021 ESO - European Southern Observatory
5 
6 @brief Implements simulation of recdaqif
7 """
8 # pylint: disable=no-name-in-module,import-error,no-self-use,redefined-builtin
9 import os
10 import json
11 import logging
12 import inspect
13 
14 from ModRecif.Recif import (
15  RecStatus,
16  RecStatusNames,
17  RecProperties,
18  RecWaitSpec,
19  RecWaitStatus,
20  RecWaitStatusNames,
21  ExceptionErr,
22 )
23 
24 from ModRecif.Recif.RecCmds import RecCmdsSyncService
25 from ModDaqsimif.Daqsimif import Error as SimCtlError
26 
27 
28 class Recording: # pylint: disable=too-many-instance-attributes,invalid-name
29  """Holds simulated status for recording"""
30 
31  def __init__(self, id):
32  self.id = id
33  self.dp_files = []
34  self.files_generated = 0
35  self.start_time = 1.0
36  self.end_time = 1.0
37  self.time_elapsed = 0.0
38  self.remaining_time = 1.0
41  self.size_recorded = 0
42  self.info = ""
43  self.status = RecStatusNames.Undefined
44 
45 
46 class Simulator: # pylint: disable=invalid-name
47  """Simulator that implements RecCmdsSyncService and sets up
48  simulation behaviour for Simulator.
49 
50  Registered hooks are run once and then removed before executed.
51  Each hook is a callable that takes the result from the default implementation
52  as well as the arguments given to the original implementation.
53  The hook can then choose to modify the existing result or replace it.
54  """
55 
56  def __init__(self, name, mal):
57  self.name = name
58  self.mal = mal
59  self.current_rec = None
60  self.recordings = {} # id: status
61  # Oneshot commands
62  self.sim_hooks = {}
63 
64  def RecStart(self, properties: RecProperties) -> RecStatus:
65  """@implements RecCmds.StartDaq"""
66  rec_id = properties.getId()
67  logging.info("Request: StartDaq(%s)", rec_id)
68  if rec_id in self.recordings:
69  raise ExceptionErr(rec_id, "Recording with id already exist")
70  self.current_rec = rec = Recording(rec_id)
71  logging.info("Request: StartDaq(%s) ref=%s", rec_id, id(rec))
72  rec.status = RecStatusNames.Active
73  self.recordings[rec_id] = rec
74  result = self.make_rec_status(rec_id)
75  return self.run_sim_hook(result, rec_id)
76 
77  def RecStop(self) -> RecStatus:
78  """@implements RecCmds.RecStop"""
79  if not self.current_rec:
80  raise ExceptionErr(1234, "No current recording active")
81  rec_id = self.current_rec.id
82  logging.info("Request: RecStop(%s)", rec_id)
83 
84  rec = self.get_recording(rec_id)
85  if rec.status in (RecStatusNames.Completed, RecStatusNames.Stopped):
86  logging.info(
87  "Request: RecStop(%s): Recording already complete. Returning error", rec_id
88  )
89  # todo: MAL has not implemented exceptions yet so I cannot throw!
90  # raise ExceptionErr(rec_id, "Recording already stopped '%s'" % rec_id)
91  logging.info(
92  "Request: RecStop(%s): ... NOT because MAL bug prevents me to", rec_id
93  )
94 
95  rec.dp_files = ["/tmp/%s.fits" % rec_id]
96  rec.status = RecStatusNames.Stopped
97 
98  result = self.make_rec_status(rec_id)
99  # Due to MAL bug we have to continue keeping the current recording alive...
100  # self.current_rec = None
101  return self.run_sim_hook(result, rec_id)
102 
103  def RecAbort(self) -> str:
104  """@implements RecCmds.RecAbort"""
105  if not self.current_rec:
106  raise ExceptionErr(1234, "No current recording active")
107 
108  rec_id = self.current_rec.id
109  logging.info("Request: RecAbort(%s)", rec_id)
110  rec = self.get_recording(rec_id)
111  rec.status = RecStatusNames.Aborted
112  self.recordings[rec_id] = rec
113  result = ""
114  self.current_rec = None
115  return self.run_sim_hook(result, rec_id)
116 
117  def RecWait(self, spec: RecWaitSpec) -> RecStatus: # pylint: disable=unused-argument
118  """@implements RecCmds.RecWait
119 
120  For the moment we simply simulate that the recording was completed
121  as soon as this request was received.
122  """
123  rec_id = self.current_rec.id
124  logging.info("Request: RecWait(%s)", rec_id)
125  result = self.make_rec_wait_status(rec_id)
126  return self.run_sim_hook(result, rec_id)
127 
128  def RecStatus(self, rec_id: str) -> RecStatus:
129  """@implements RecCmds.RecStatus"""
130  logging.info("Request: RecStatus(%s)", rec_id)
131  result = self.make_rec_status(rec_id)
132  return self.run_sim_hook(result, rec_id)
133 
134  def get_recording(self, rec_id) -> Recording:
135  """Get recording or raise ExceptionErr."""
136  if rec_id in self.recordings:
137  return self.recordings[rec_id]
138  raise ExceptionErr(rec_id, "Unknown recording id '%s'" % rec_id)
139 
140  def make_rec_status(self, rec_id) -> RecStatus:
141  """
142  <struct name="RecStatus">
143  <member name="dpFiles" type="string" arrayDimensions="(32)"/>
144  <member name="endTime" type="double"/>
145  <member name="filesGenerated" type="int32_t"/>
146  <member name="framesProcessed" type="int32_t"/>
147  <member name="framesRemaining" type="int32_t"/>
148  <member name="id" type="string"/>
149  <member name="info" type="string"/>
150  <member name="remainingTime" type="double"/>
151  <member name="sizeRecorded" type="int32_t"/>
152  <member name="startTime" type="double"/>
153  <member name="status" type="nonBasic" nonBasicTypeName="RecStatusNames"/>
154  <member name="timeElapsed" type="double"/>
155  </struct>
156  """
157  logging.debug("Creating return type: RecStatus")
158  rec = self.get_recording(rec_id)
159  status = self.mal.createDataEntity(RecStatus)
160  self.populate_rec_status(rec, status)
161  return status
162 
163  def populate_rec_status(self, rec: Recording, status: RecStatus) -> RecStatus:
164  """Populates RecStatus from status"""
165  logging.debug("Populating return type: RecStatus: id=%s, status=%r", rec.id, rec.status)
166  status.setDpFiles(rec.dp_files)
167  status.setEndTime(rec.end_time)
168  status.setFilesGenerated(rec.files_generated)
169  status.setFramesProcessed(rec.frames_processed)
170  status.setFramesRemaining(rec.frames_remaining)
171  status.setId(rec.id)
172  status.setInfo(rec.info)
173  status.setRemainingTime(rec.remaining_time)
174  status.setSizeRecorded(rec.size_recorded)
175  status.setStartTime(rec.start_time)
176  status.setStatus(rec.status)
177  status.setTimeElapsed(rec.time_elapsed)
178 
179  def make_rec_wait_status(self, rec_id) -> RecWaitStatus:
180  """Creates RecWaitStatus and populates attributes"""
181  logging.debug("Creating return type: RecWaitStatus")
182  rec = self.get_recording(rec_id)
183  status = self.mal.createDataEntity(RecWaitStatus)
184  self.populate_rec_status(rec, status.getRecStatus())
185  if rec.status in (RecStatusNames.Completed, RecStatusNames.Stopped):
186  logging.debug("Recording is Completed or Stopped, RecWaitStatus set to success")
187  status.setStatus(RecWaitStatusNames.Success)
188  else:
189  status.setStatus(RecWaitStatusNames.Timeout)
190  return status
191 
192  def add_sim_hook(self, cmd_name: str, hook):
193  """Add the simulation hook `hook` for the specified command `cmd_name` (e.g.
194  "RecStart").
195 
196  Any previous hooks will be replaced.
197  """
198  self.sim_hooks[cmd_name] = hook
199 
200  def reset(self):
201  """Reset simulator to initial default state."""
202  self.clear_sim_hooks()
203  self.recordings = {}
204  self.current_rec = None
205 
206  def clear_sim_hooks(self):
207  """Remove all simulation hooks."""
208  self.sim_hooks = {}
209 
210  def run_sim_hook(self, result, *args):
211  """Runs simulation hook that may modify or replace the default implementation.
212 
213  The hook to execute is automatically determined from the call stack, so
214  this should only ever be executed directly from the service command
215  implementation method.
216  """
217  cmd_name = inspect.stack()[1][3]
218  hook = self.sim_hooks.get(cmd_name)
219  logging.debug("Checking for hook for command %s", cmd_name)
220  if hook:
221  logging.debug("Running simulation hook for command %s", cmd_name)
222  del self.sim_hooks[cmd_name]
223  return hook(result, *args)
224  return result
225 
226 
227 class SimulatorCtl: # pylint: disable=invalid-name
228  """Simulator controller that implements ModDaqsimif and sets up
229  simulation behaviour for Simulator.
230  """
231 
232  def __init__(self, name, mal, server):
233  self.name = name
234  self.mal = mal
235  self.server = server
236 
237  self.sim = Simulator(name, mal)
238  logging.info("Registering service 'rec'")
239  self.server.registerService("rec", RecCmdsSyncService, self.sim)
240 
241  def Setup(self, spec):
242  """@implements SimCtl.Setup."""
243  logging.info('SimCtl.Setup: spec="%s"', spec)
244  s = json.loads(spec)
245  try:
246  cmd = s.get("command", "")
247  action = s.get("action", "")
248  if action == "complete-recording":
249  # finish current recording
250  rec = self.sim.current_rec
251  logging.info("SimCtl.Setup: Marking %s as Completed ref=%s", rec.id, id(rec))
252  rec.status = RecStatusNames.Completed
253  rec.dp_files = ["/tmp/auto-%s.fits" % rec.id]
254  elif action == "throw":
255 
256  def throw(res, *args):
257  raise ExceptionErr(self.sim.current_rec.id, "Simulated error")
258 
259  self.sim.add_sim_hook(cmd, throw)
260  else:
261  raise SimCtlError("Unknown action '%s' provided" % action)
262  logging.info("SimCtl.Setup: Returning empty string")
263  return json.dumps("")
264  except SimCtlError:
265  raise
266  except Exception as e:
267  raise SimCtlError('"%s"' % str(e))
268 
269  def Reset(self):
270  """@implements SimCtl.Reset"""
271  try:
272  self.sim.reset()
273  except Exception as e:
274  raise SimCtlError(str(e))
275 
276  def ForceExit(self):
277  """@implements SimCtl.ForceExit."""
278  # Notify main thread that it should exit
279  logging.warning("Force Exiting without cleanup!")
280  os._exit() # pylint: disable=protected-access
recifsim.simulator.Recording.time_elapsed
time_elapsed
Definition: simulator.py:37
recifsim.simulator.Recording.__init__
def __init__(self, id)
Definition: simulator.py:31
recifsim.simulator.Simulator.add_sim_hook
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:197
recifsim.simulator.Simulator.recordings
recordings
Definition: simulator.py:60
recifsim.simulator.SimulatorCtl.Reset
def Reset(self)
Definition: simulator.py:270
recifsim.simulator.Simulator.get_recording
Recording get_recording(self, rec_id)
Get recording or raise ExceptionErr.
Definition: simulator.py:135
recifsim.simulator.Simulator.make_rec_wait_status
RecWaitStatus make_rec_wait_status(self, rec_id)
Creates RecWaitStatus and populates attributes.
Definition: simulator.py:180
recifsim.simulator.Simulator.mal
mal
Definition: simulator.py:58
recifsim.simulator.Recording.dp_files
dp_files
Definition: simulator.py:33
recifsim.simulator.SimulatorCtl.name
name
Definition: simulator.py:233
recifsim.simulator.Simulator.RecStart
RecStatus RecStart(self, RecProperties properties)
Definition: simulator.py:65
recifsim.simulator.Simulator.RecStatus
RecStatus RecStatus(self, str rec_id)
Definition: simulator.py:129
recifsim.simulator.SimulatorCtl.mal
mal
Definition: simulator.py:234
recifsim.simulator.Simulator.current_rec
current_rec
Definition: simulator.py:59
recifsim.simulator.SimulatorCtl.Setup
def Setup(self, spec)
Definition: simulator.py:242
recifsim.simulator.Simulator.clear_sim_hooks
def clear_sim_hooks(self)
Remove all simulation hooks.
Definition: simulator.py:207
recifsim.simulator.Recording.files_generated
files_generated
Definition: simulator.py:34
recifsim.simulator.SimulatorCtl
Simulator controller that implements ModDaqsimif and sets up simulation behaviour for Simulator.
Definition: simulator.py:230
recifsim.simulator.SimulatorCtl.__init__
def __init__(self, name, mal, server)
Definition: simulator.py:232
recifsim.simulator.Recording.remaining_time
remaining_time
Definition: simulator.py:38
recifsim.simulator.Simulator.populate_rec_status
RecStatus populate_rec_status(self, Recording rec, RecStatus status)
Populates RecStatus from status.
Definition: simulator.py:164
recifsim.simulator.Recording.status
status
Definition: simulator.py:43
recifsim.simulator.Simulator.RecStop
RecStatus RecStop(self)
Definition: simulator.py:78
recifsim.simulator.Recording.frames_processed
frames_processed
Definition: simulator.py:39
recifsim.simulator.Simulator.make_rec_status
RecStatus make_rec_status(self, rec_id)
Definition: simulator.py:156
recifsim.simulator.Simulator
Simulator that implements RecCmdsSyncService and sets up simulation behaviour for Simulator.
Definition: simulator.py:54
recifsim.simulator.Simulator.RecWait
RecStatus RecWait(self, RecWaitSpec spec)
Definition: simulator.py:122
recifsim.simulator.Recording.id
id
Definition: simulator.py:32
recifsim.simulator.Recording
Holds simulated status for recording.
Definition: simulator.py:29
recifsim.simulator.SimulatorCtl.server
server
Definition: simulator.py:235
recifsim.simulator.Recording.end_time
end_time
Definition: simulator.py:36
recifsim.simulator.Simulator.name
name
Definition: simulator.py:57
recifsim.simulator.Recording.info
info
Definition: simulator.py:42
recifsim.simulator.Simulator.reset
def reset(self)
Reset simulator to initial default state.
Definition: simulator.py:201
recifsim.simulator.SimulatorCtl.sim
sim
Definition: simulator.py:237
recifsim.simulator.Simulator.sim_hooks
sim_hooks
Definition: simulator.py:62
recifsim.simulator.SimulatorCtl.ForceExit
def ForceExit(self)
Definition: simulator.py:277
recifsim.simulator.Simulator.RecAbort
str RecAbort(self)
Definition: simulator.py:104
recifsim.simulator.Simulator.run_sim_hook
def run_sim_hook(self, result, *args)
Runs simulation hook that may modify or replace the default implementation.
Definition: simulator.py:216
recifsim.simulator.Simulator.__init__
def __init__(self, name, mal)
Definition: simulator.py:56
recifsim.simulator.Recording.size_recorded
size_recorded
Definition: simulator.py:41
recifsim.simulator.Recording.frames_remaining
frames_remaining
Definition: simulator.py:40
recifsim.simulator.Recording.start_time
start_time
Definition: simulator.py:35