class TestAttribute(unittest.TestCase): def setUp(self): self.data = StringMeta().create_attribute_model() self.data.set_notifier_path(Mock(), ["block", "attr"]) self.controller = Mock() self.context = Mock() self.o = Attribute(self.controller, self.context, self.data) def test_init(self): self.assertIsInstance(self.o, Attribute) assert hasattr(self.o, "meta") assert hasattr(self.o, "value") assert hasattr(self.o, "subscribe_value") def test_put(self): self.o.put_value(32) self.context.put.assert_called_once_with(["block", "attr", "value"], 32, timeout=None) def test_put_async(self): f = self.o.put_value_async(32) self.context.put_async.assert_called_once_with( ["block", "attr", "value"], 32) assert f == self.context.put_async.return_value def test_repr(self): self.context.make_view.return_value = "foo" assert repr(self.o) == "<Attribute value='foo'>" self.context.make_view.assert_called_once_with(self.controller, self.data, "value")
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 create_attributes(self): self.value = Attribute(NumberMeta(description="Value")) self.value.meta.set_dtype('float64') yield 'value', self.value self.generator = Attribute( PointGeneratorMeta(description="Scan Point Generator")) yield "generator", self.generator self.axis_name = Attribute(StringMeta(description="Name of the axis")) yield "axis_name", self.axis_name self.exposure = Attribute(NumberMeta(description="Exposure time")) self.value.meta.set_dtype('float64') yield "exposure", self.exposure
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_create_children(self, attribute_mock, method_mock): # Load up items to create mi1, mi2 = MagicMock(), MagicMock() method_mock.side_effect = [mi1, mi2] ai1, ai2 = MagicMock(), MagicMock() attribute_mock.side_effect = [ai1, ai2] # Load up refs to get group_attr = Attribute() child_attr = self.make_grouped_attr() m1 = MethodMeta() m2 = MethodMeta() BlockItem.items[("endpoint", "foo")] = ai1 self.item.ref = OrderedDict(( ("foo", group_attr), ("c", child_attr), ("a", m1), ("b", m2))) self.item.create_children() # Check it made the right thing self.assertEqual(len(self.item.children), 3) attribute_mock.assert_any_call(("endpoint", "foo"), group_attr) self.assertEqual(self.item.children[0], ai1) attribute_mock.assert_any_call(("endpoint", "c"), child_attr) ai1.add_child.assert_called_once_with(ai2) method_mock.assert_any_call(("endpoint", "a"), m1) self.assertEqual(self.item.children[1], mi1) method_mock.assert_any_call(("endpoint", "b"), m2) self.assertEqual(self.item.children[2], mi2)
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("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_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)
class ScanPointTickerController(Controller): def create_attributes(self): self.value = Attribute(NumberMeta(description="Value")) self.value.meta.set_dtype('float64') yield 'value', self.value self.generator = Attribute( PointGeneratorMeta(description="Scan Point Generator")) yield "generator", self.generator self.axis_name = Attribute(StringMeta(description="Name of the axis")) yield "axis_name", self.axis_name self.exposure = Attribute(NumberMeta(description="Exposure time")) self.value.meta.set_dtype('float64') yield "exposure", self.exposure @takes("generator", PointGeneratorMeta( description="Generator instance"), REQUIRED, "axis_name", StringMeta( description="Specifier for axis"), REQUIRED, "exposure", NumberMeta( description="Detector exposure time"), REQUIRED) def configure(self, params): """ Configure the controller Args: generator(PointGenerator): Generator to create points axis_name(String): Specifier for axis exposure(Double): Exposure time for detector """ self.generator.set_value(params.generator) self.axis_name.set_value(params.axis_name) self.exposure.set_value(params.exposure) self.exposure.meta.set_dtype('float64') self.block.notify_subscribers() @Method.wrap_method def run(self): """ Start the ticker process Yields: Point: Scan points from PointGenerator """ axis_name = self.axis_name.value for point in self.generator.value.iterator(): self.value.set_value(point.positions[axis_name]) self.block.notify_subscribers() time.sleep(self.exposure.value)
class CounterController(Controller): def create_attributes(self): self.counter = Attribute(NumberMeta(description="A counter")) self.counter.meta.set_dtype('uint32') self.counter.set_put_function(self.counter.set_value) self.counter.set_value(0) yield "counter", self.counter @Controller.Resetting def do_reset(self): self.counter.set_value(0, notify=False) # Transition will do the notify for us @takes() def increment(self): self.counter.set_value(self.counter.value + 1)
class ScanPointTickerController(Controller): def create_attributes(self): self.value = Attribute(NumberMeta(description="Value")) self.value.meta.set_dtype('float64') yield 'value', self.value self.generator = Attribute( PointGeneratorMeta(description="Scan Point Generator")) yield "generator", self.generator self.axis_name = Attribute(StringMeta(description="Name of the axis")) yield "axis_name", self.axis_name self.exposure = Attribute(NumberMeta(description="Exposure time")) self.value.meta.set_dtype('float64') yield "exposure", self.exposure @takes("generator", PointGeneratorMeta(description="Generator instance"), REQUIRED, "axis_name", StringMeta(description="Specifier for axis"), REQUIRED, "exposure", NumberMeta(description="Detector exposure time"), REQUIRED) def configure(self, params): """ Configure the controller Args: generator(PointGenerator): Generator to create points axis_name(String): Specifier for axis exposure(Double): Exposure time for detector """ self.generator.set_value(params.generator) self.axis_name.set_value(params.axis_name) self.exposure.set_value(params.exposure) self.exposure.meta.set_dtype('float64') self.block.notify_subscribers() @Method.wrap_method def run(self): """ Start the ticker process Yields: Point: Scan points from PointGenerator """ axis_name = self.axis_name.value for point in self.generator.value.iterator(): self.value.set_value(point.positions[axis_name]) self.block.notify_subscribers() time.sleep(self.exposure.value)
def create_attributes(self): params = self.params if params.pv is None and params.rbv is None: raise ValueError('must pass pv rbv') if params.rbv is None: if params.rbv_suff is None: params.rbv = params.pv else: params.rbv = params.pv + params.rbv_suff # Meta instance self.name = params.name self.meta = self.create_meta(params.description) # Pv strings self.pv = params.pv self.rbv = params.rbv # camonitor subscription self.monitor = None self.ca_format = catools.FORMAT_CTRL # This will be our attr self.attr = None # The attribute we will be publishing self.attr = Attribute(self.meta) self.attr.set_put_function(self.caput) yield self.name, self.attr
def _fill_sequencer(self, seq_table: Attribute) -> None: assert self.generator, "No generator" points = self.generator.get_points(self.loaded_up_to, self.scan_up_to) if points is None or len(points) == 0: table = SequencerTable.from_rows([]) seq_table.put_value(table) return rows = [] if not self.axis_mapping: # No position compare or row triggering required rows.extend(self._generate_immediate_rows(points.duration)) # one last dead frame signal rows.append(seq_row(half_duration=LAST_PULSE, dead=1)) if len(rows) > SEQ_TABLE_ROWS: raise Exception("Seq table: {} rows with {} maximum".format( len(rows), SEQ_TABLE_ROWS)) table = SequencerTable.from_rows(rows) seq_table.put_value(table) return start_indices, end_indices = self._get_row_indices(points) point = points[0] first_point_static = point.positions == point.lower == point.upper end = start_indices[0] if start_indices.size else len(points) if not first_point_static: # If the motors are moving during this point then # wait for triggers rows.extend(self._generate_triggered_rows(points, 0, end, False)) else: # This first row should not wait, and will trigger immediately rows.extend(self._generate_immediate_rows(points.duration[0:end])) for start, end in zip(start_indices, end_indices): # First row handled outside of loop self.last_point = points[start - 1] rows.extend(self._generate_triggered_rows(points, start, end, True)) # one last dead frame signal rows.append(seq_row(half_duration=LAST_PULSE, dead=1)) if len(rows) > SEQ_TABLE_ROWS: raise Exception("Seq table: {} rows with {} maximum".format( len(rows), SEQ_TABLE_ROWS)) table = SequencerTable.from_rows(rows) seq_table.put_value(table)
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 test_grouped_children(self): attr = self.make_grouped_attr() self.item.ref = dict(c=attr, d=Attribute()) self.assertEqual(self.item.ref_children(), 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)
class CAPart(Part): def create_attributes(self): params = self.params if params.pv is None and params.rbv is None: raise ValueError('must pass pv rbv') if params.rbv is None: if params.rbv_suff is None: params.rbv = params.pv else: params.rbv = params.pv + params.rbv_suff # Meta instance self.name = params.name self.meta = self.create_meta(params.description) # Pv strings self.pv = params.pv self.rbv = params.rbv # camonitor subscription self.monitor = None self.ca_format = catools.FORMAT_CTRL # This will be our attr self.attr = None # The attribute we will be publishing self.attr = Attribute(self.meta) self.attr.set_put_function(self.caput) yield self.name, self.attr def create_meta(self, description): raise NotImplementedError def get_datatype(self): raise NotImplementedError @Controller.Resetting def connect_pvs(self): # release old monitor self.close_monitor() # make the connection in cothread's thread, use caget for initial value pvs = [self.rbv] if self.pv: pvs.append(self.pv) ca_values = cothread.CallbackResult( catools.caget, pvs, format=self.ca_format, datatype=self.get_datatype()) # check connection is ok for i, v in enumerate(ca_values): assert v.ok, "CA connect failed with %s" % v.state_strings[v.state] self.update_value(ca_values[0]) # now setup monitor on rbv self.monitor = catools.camonitor( self.rbv, self.on_update, notify_disconnect=True, format=self.ca_format, datatype=self.get_datatype()) def close_monitor(self): if self.monitor is not None: cothread.CallbackResult(self.monitor.close) self.monitor = None def caput(self, value): cothread.CallbackResult( catools.caput, self.pv, value, wait=True, timeout=None, datatype=self.get_datatype()) # now do a caget value = cothread.CallbackResult( catools.caget, self.rbv, format=self.ca_format, datatype=self.get_datatype()) self.update_value(value) def on_update(self, value): # Called on cothread's queue, so don't block self.process.spawn(self.update_value, value) def update_value(self, value): self.log_debug("Camonitor update %r", value) # TODO: make Alarm from value.status and value.severity # TODO: make Timestamp from value.timestamp if not value.ok: # TODO: set disconnect self.attr.set_value(None) else: # update value self.attr.set_value(value)
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
def create_attributes(self): self.counter = Attribute(NumberMeta(description="A counter")) self.counter.meta.set_dtype('uint32') self.counter.set_put_function(self.counter.set_value) self.counter.set_value(0) yield "counter", self.counter
def make_grouped_attr(self): attr = Attribute() attr.set_endpoint_data("meta", MagicMock()) attr.meta.tags = ["group:foo"] return attr
def setUp(self): self.data = StringMeta().create_attribute_model() self.data.set_notifier_path(Mock(), ["block", "attr"]) self.controller = Mock() self.context = Mock() self.o = Attribute(self.controller, self.context, self.data)
def test_ref_children(self): self.item.ref = dict( a=MethodMeta(), b=MethodMeta(), c=Attribute()) self.assertEqual(self.item.ref_children(), 3)