def check_set(self, attr, expected): self.assertEqual(self.s.attributes[attr].pv.caput.call_count, 1) call_args = self.s.attributes[attr].pv.caput.call_args val = call_args[0][0] self.assertEquals( val, expected, "{}: expected {} got {}".format(attr, expected, val)) Attribute.update(self.s.attributes[attr], val) self.s.attributes[attr].pv.reset_mock()
def set_configured(self): # Set all the pvs to the right value for attr in sorted(self.send_params): self.s.attributes[attr]._value = self.send_params[attr] self.s.configure(block=False, **self.in_params) Attribute.update(self.s.attributes["delete"], True) cothread.Yield() Attribute.update(self.s.attributes["xml"], self.s._sconfig.seq_items[1].seq_params["xml"]) cothread.Yield() self.assertEqual(self.s.state, DState.Ready)
def set_configured(self): # Set all the pvs to the right value for attr in sorted(self.send_params): self.s.attributes[attr]._value = self.send_params[attr] self.s.configure(block=False, **self.in_params) cothread.Yield() self.check_set("positionMode", True) Attribute.update( self.s.attributes["xml"], self.s._sconfig.seq_items[1].seq_params["xml"]) cothread.Yield() self.assertEqual(self.s.state, DState.Ready) self.s.attributes["writeStatus"]._value = "Ok"
def test_run(self): self.set_configured() # Do a run spawned = cothread.Spawn(self.s.run) cothread.Yield() cothread.Yield() self.assertEqual(self.s.stateMachine.state, DState.Running) self.check_set("capture", 1) self.assertEqual(self.s.capture, 1) Attribute.update(self.s.attributes["capture"], False) cothread.Yield() self.assertEqual(self.s.stateMachine.state, DState.Idle) spawned.Wait(1) self.assertEqual(self.s.stateMachine.state, DState.Idle)
def test_run(self): self.set_configured() spawned = cothread.Spawn(self.s.run) # Yield to let run run cothread.Yield() self.assertEqual(self.s.state, DState.Ready) # Yield to let do_run run cothread.Yield() self.assertEqual(self.s.state, DState.Running) self.check_set("acquire", 1) self.assertEqual(self.s.acquire, 1) Attribute.update(self.s.attributes["acquire"], False) cothread.Yield() self.assertEqual(self.s.stateMachine.state, DState.Idle) spawned.Wait(1) self.assertEqual(self.s.stateMachine.state, DState.Idle)
def test_abort(self): self.set_configured() spawned = cothread.Spawn(self.s.run) cothread.Yield() cothread.Yield() self.assertEqual(self.s.stateMachine.state, DState.Running) self.check_set("acquire", 1) self.assertEqual(self.s.acquire, 1) aspawned = cothread.Spawn(self.s.abort) cothread.Yield() cothread.Yield() self.assertEqual(self.s.stateMachine.state, DState.Aborting) self.check_set("acquire", 0) Attribute.update(self.s.attributes["acquire"], False) cothread.Yield() self.assertEqual(self.s.stateMachine.state, DState.Aborted) spawned.Wait(1) aspawned.Wait(1)
def test_abort(self): self.set_configured() spawned = cothread.Spawn(self.s.run) cothread.Yield() cothread.Yield() self.assertEqual(self.s.stateMachine.state, DState.Running) self.check_set("scanStart", 1) self.assertEqual(self.s.scanStart, 1) Attribute.update(self.s.attributes["progState"], "Scanning") cothread.Yield() self.assertEqual(self.s.stateMachine.state, DState.Running) aspawned = cothread.Spawn(self.s.abort) cothread.Yield() Attribute.update(self.s.attributes["progState"], "Idle") cothread.Yield() self.assertEqual(self.s.stateMachine.state, DState.Aborted) spawned.Wait(1) aspawned.Wait(1)
def test_mismatch(self): self.set_configured() Attribute.update(self.s.attributes["arrayCallbacks"], False) self.assertEqual(self.s.state, DState.Ready) cothread.Yield() self.assertEqual(self.s.state, DState.Idle)
def test_mismatch(self): self.set_configured() Attribute.update(self.s.attributes["extraDimSizeN"], 2) self.assertEqual(self.s.stateMachine.state, DState.Ready) cothread.Yield() self.assertEqual(self.s.stateMachine.state, DState.Idle)
class SimDetector(PausableDevice): @wrap_method( simDetector=InstanceAttribute(SimDetectorDriver, "SimDetectorDriver Device"), hdf5Writer=InstanceAttribute(Hdf5Writer, "Hdf5Writer Device"), positionPlugin=InstanceAttribute( PositionPlugin, "PositionPlugin Device"), ) def __init__(self, name, simDetector, positionPlugin, hdf5Writer): super(SimDetector, self).__init__(name) self.simDetectorDriver = simDetector self.positionPlugin = positionPlugin self.hdf5Writer = hdf5Writer self.children = [simDetector, positionPlugin, hdf5Writer] # Child state machine listeners for c in self.children: c.add_listener(self.post_changes, "stateMachine") self.child_statemachines = [c.stateMachine for c in self.children] # Run monitors hdf5Writer.add_listener( self.post_changes, "attributes.uniqueId") hdf5Writer.add_listener( self.post_changes, "attributes.capture") positionPlugin.add_listener( self.post_changes, "attributes.running") def add_all_attributes(self): super(SimDetector, self).add_all_attributes() # Configure self._thing = None self.exposure = Attribute(VDouble, "Exposure time for each frame") self.exposure = 1.2 if self.exposure > 0: print "blah" self.exposure.update(1.2, alarm=Alarm.disconnected()) if self.exposure._value > 0: print "blah" self.period = Attribute(VDouble, "Time between the start of each frame") self.positions = Attribute( VTable, "Position table, column headings are dimension names, " "slowest changing first") self.hdf5File = Attribute(VString, "HDF5 full file path to write") # Monitor self.dimensions = Attribute( VIntArray, "Detected dimensionality of positions") def _validate_hdf5Writer(self, hdf5File, positions, dimensions, totalSteps): filePath, fileName = os.path.split(hdf5File) dimNames = [] dimUnits = [] names = [c[0] for c in positions] indexNames = [n for n in names if n.endswith("_index")] for name, _, _, unit in positions: if not name.endswith("_index"): dimNames.append(name) if unit: dimUnits.append(unit) else: dimUnits.append("mm") assert len(dimNames) == len(dimensions), \ "Can't unify position number of index columns {} with " \ "dimensions {}".format(len(dimNames), dimensions) return self.hdf5Writer.validate( filePath, fileName, dimNames, dimUnits, indexNames, dimensions) @wrap_method() def validate(self, hdf5File, exposure, positions=def_positions, period=None): # Validate self dimensions, positions = self._add_position_indexes(positions) # Validate simDetectorDriver totalSteps = len(positions[0][2]) sim_params = self.simDetectorDriver.validate(exposure, totalSteps, period) runTime = sim_params["runTime"] runTimeout = sim_params["runTimeout"] period = sim_params["period"] # Validate position plugin self.positionPlugin.validate(positions) # Validate hdf writer self._validate_hdf5Writer(hdf5File, positions, dimensions, totalSteps) return super(SimDetector, self).validate(locals()) def _add_position_indexes(self, positions): # which columns are index columns? names = [column[0] for column in positions] indexes = [n[:-len("_index")] for n in names if n.endswith("_index")] non_indexes = [n for n in names if not n.endswith("_index")] expected_indexes = ["{}_index".format(n) for n in non_indexes] # check if indexes are supplied if indexes == expected_indexes or indexes == ["n_index"]: # just get dimensionality from these indexes dims = [max(d) + 1 for n, _, d, _ in positions if n in indexes] index_columns = [c for c in positions if n in indexes] else: # detect dimensionality of non_index columns uniq = [sorted(set(d)) for n, _, d, _ in positions if n in non_indexes] dims = [len(pts) for pts in uniq] npts = len(positions[0][2]) if numpy.prod(dims) != npts: # This is a sparse scan, should be written as long list dims = [npts] index_columns = [ ("n_index", VInt, numpy.arange(npts, dtype=numpy.int32), '')] else: # Create position table index_columns = [] for name, sort in zip(non_indexes, uniq): index = "{}_index".format(name) # select the correct named column data = [d for n, _, d, _ in positions if n == name][0] # work out their index in the unique sorted list data = numpy.array([sort.index(x) for x in data], dtype=numpy.int32) index_columns.append((index, VInt, data, '')) positions = [c for c in positions if n in non_indexes] + index_columns return dims, positions def _configure_simDetectorDriver(self): self.simDetectorDriver.configure( self.exposure, self.totalSteps - self.currentStep, self.period, self.currentStep, block=False) def _configure_positionPlugin(self): if self.currentStep > 0: positions = [] for n, t, d, u in self.positions: positions.append([n, t, d[self.currentStep:], u]) else: positions = self.positions assert self.simDetectorDriver.portName is not None, \ "Expected simDetectorDriver.portName != None" self.positionPlugin.configure( positions, self.currentStep + 1, self.simDetectorDriver.portName, block=False) def _configure_hdf5Writer(self): params = self._validate_hdf5Writer(self.hdf5File, self.positions, self.dimensions, self.totalSteps) params = {k: v for k, v in params.items() if k in self.hdf5Writer.configure.arguments} assert self.positionPlugin.portName is not None, \ "Expected positionPlugin.portName != None" params.update(arrayPort=self.positionPlugin.portName, block=False) self.hdf5Writer.configure(**params) def do_configure(self, config_params, task): """Start doing a configuration using config_params. Return DState.Configuring, message when started """ for d in self.children: assert d.state in DState.canConfig(), \ "Child device {} in state {} is not configurable"\ .format(d, d.state) # Setup self for name, value in config_params.items(): setattr(self, name, value) self.stepsPerRun = 1 self.currentStep = 0 task.report("Configuring simDetectorDriver") self._configure_simDetectorDriver() task.report("Configuring positionPlugin") self._configure_positionPlugin() # Setup config matcher self._sconfig = ConfigMatcher( SeqTransitionItem( self.child_statemachines, DState.Ready, DState.rest())) name, changes = task.report_wait("Wait for plugins to configure") while not self._sconfig.check_done(name, changes): name, changes = task.report_wait() task.report("Configuring hdf5Writer") self._configure_hdf5Writer(self.positionPlugin.dimensions) # Finished task.report("Configuring done", DState.Ready) def do_ready(self, value, changes): """Work out if the changes mean we are still ready for run. Return None, message if it is still ready. Return DState.Idle, message if it isn't still ready. """ mismatches = self._sconfig.mismatches() if mismatches: yield "Unconfigured: {}".format(mismatches), DState.Idle def do_run(self): """Start doing a run. Return DState.Running, message when started """ plugins = [self.simDetectorDriver.stateMachine, self.positionPlugin.stateMachine] for d in plugins: assert d.state == DState.Ready, \ "Child device {} in state {} is not runnable"\ .format(d.name, d.state) self.report("Running positionPlugin") self.positionPlugin.run(block=False) # If hdf writer is not already running then run it if self.hdf5Writer.state != DState.Running: self.hdf5Writer.run(block=False) t = SeqTransitionItem(self.hdf5Writer.attributes["capture"], True) name, changes = yield "Wait for hdf5Writer to run" while not t.check_done(name, changes): name, changes = yield "Wait for hdf5Writer to run" # Now start the other plugins t = SeqTransitionItem(self.positionPlugin.attributes["running"], True) name, changes = yield "Wait for hdf5Writer to run" while not t.check_done(name, changes): name, changes = yield "Wait for hdf5Writer to run" seq_items += [ SeqTransitionItem( "Wait for positionPlugin to run", self.positionPlugin.attributes["running"], True), SeqFunctionItem( "Running simDetectorDriver", self.simDetectorDriver.run, block=False), SeqTransitionItem( "Wait for run to finish", self.child_statemachines, DState.Idle, DState.rest()), ] # Add a configuring object self._srun = Sequence(self.name + ".SRun", *seq_items) # Start the sequence item_done, msg = self._srun.start() if item_done: # Arrange for a callback to process the next item self.post_changes(None, None) return DState.Running, msg def do_running(self, value, changes): """Work out if the changes mean running is complete. Return None, message if it isn't. Return DState.Idle, message if it is and we are all done Return DState.Ready, message if it is and we are partially done """ # Update progress if value == self.hdf5Writer.attributes["uniqueId"]: self.currentStep = value.value running, item_done, msg = self._srun.process(value, changes) if running is False: # Finished return DState.Idle, "Running done" elif item_done: # Arrange for a callback to process the next item self.post_changes(None, None) # Still going return DState.Running, msg def do_abort(self): """Stop acquisition """ if self.state == DState.Configuring: self._sconfig.abort() elif self.state == DState.Running: self._srun.abort() elif self.state == DState.Rewinding: self._spause.abort() for d in self.children: if d.state in DState.canAbort(): d.abort(block=False) self.post_changes(None, None) return DState.Aborting, "Aborting started" def do_aborting(self, value, changes): """Work out if the changes mean aborting is complete. Return None, message if it isn't. Return DState.Aborted, message if it is. """ child_states = [c.state for c in self.children] rest = [s in DState.rest() for s in child_states] if all(rest): # All are in rest states no_fault = [s != DState.Fault for s in child_states] assert all(no_fault), \ "Expected no fault, got {}".format(child_states) return DState.Aborted, "Aborting finished" else: # No change return None, None def do_reset(self): """Start doing a reset from aborted or fault state. Return DState.Resetting, message when started """ seq_items = [] # Abort any items that need to be aborted need_wait = [] need_reset = [] for d in self.children: if d.state not in DState.rest(): d.abort(block=False) need_wait.append(d.stateMachine) need_reset.append(d) elif d.state in DState.canReset(): need_reset.append(d) if need_wait: seq_items.append(SeqTransitionItem( "Wait for plugins to stop aborting", need_wait, DState.rest())) if need_reset: for d in need_reset: seq_items.append(SeqFunctionItem( "Reset {}".format(d.name), d.reset, block=False)) seq_items.append(SeqTransitionItem( "Wait for plugins to stop resetting", [d.stateMachine for d in need_reset], DState.rest())) # Add a resetting object if seq_items: self._sreset = Sequence(self.name + ".SReset", *seq_items) # Start the sequence item_done, msg = self._sreset.start() if item_done: # Arrange for a callback to process the next item self.post_changes(None, None) else: self._sreset = None msg = "Started resetting" self.post_changes(None, None) return DState.Resetting, msg def do_resetting(self, value, changes): """Work out if the changes mean resetting is complete. Return None, message if it isn't. Return DState.Idle, message if it is. """ if self._sreset is None: return DState.Idle, "Resetting done" running, item_done, msg = self._sreset.process(value, changes) if running is False: # Finished child_states = [d.state for d in self.children] nofault = [s != DState.Fault for s in child_states] assert all(nofault), \ "Expected all not in fault, got {}".format(child_states) return DState.Idle, "Resetting done" elif item_done: # Arrange for a callback to process the next item self.post_changes(None, None) # Still going return DState.Resetting, msg def do_rewind(self, steps=None): """Start a pause""" # make some sequences for config plugins = [self.simDetectorDriver.stateMachine, self.positionPlugin.stateMachine] for d in plugins: assert d.state in DState.canAbort(), \ "Child device {} in state {} is not abortable"\ .format(d, d.state) seq_items = [] # if we need to abort if self.simDetectorDriver.state not in DState.canConfig() or \ self.positionPlugin.state not in DState.canConfig(): seq_items += [ SeqFunctionItem( "Stopping simDetectorDriver", self.simDetectorDriver.abort, block=False), SeqFunctionItem( "Stopping positionPlugin", self.positionPlugin.abort, block=False), SeqTransitionItem( "Wait for plugins to stop", plugins, DState.Aborted, DState.rest()), SeqFunctionItem( "Reset simDetectorDriver", self.simDetectorDriver.reset, block=False), SeqFunctionItem( "Reset positionPlugin", self.positionPlugin.reset, block=False), SeqTransitionItem( "Wait for plugins to reset", plugins, DState.Idle, DState.rest()) ] # Add the config stages seq_items += [ SeqFunctionItem( "Configuring positionPlugin", self._configure_positionPlugin), SeqFunctionItem( "Configuring simDetectorDriver", self._configure_simDetectorDriver), SeqTransitionItem( "Wait for plugins to configure", plugins, DState.Ready, DState.rest()), ] # Add a configuring object self._spause = Sequence(self.name + ".SPause", *seq_items) if self.state == DState.Ready: self._post_rewind_state = DState.Ready else: self._post_rewind_state = DState.Paused if steps is not None: self.currentStep -= steps # Start the sequence item_done, msg = self._spause.start() if item_done: # Arrange for a callback to process the next item self.post_changes(None, None) return DState.Rewinding, msg def do_rewinding(self, value, changes): """Receive run status events and move to next state when finished""" running, item_done, msg = self._spause.process(value, changes) if running is False: # Finished return self._post_rewind_state, "Rewinding done" elif item_done: # Arrange for a callback to process the next item self.post_changes(None, None) # Still going return None, msg