class CounterPart(Part): # Attribute for the counter value counter = None def create_attributes(self): self.counter = NumberMeta("uint32", "A counter").make_attribute(0) yield "counter", self.counter, self.counter.set_value @method_takes() def zero(self): self.counter.set_value(0) @method_takes() def increment(self): self.counter.set_value(self.counter.value + 1)
class CounterPart(Part): # Attribute for the counter value counter = None def create_attributes(self): self.counter = NumberMeta("float64", "A counter").make_attribute() yield "counter", self.counter, self.counter.set_value @method_takes() def zero(self): """Zero the counter attribute""" self.counter.set_value(0) @method_takes() def increment(self): """Add one to the counter attribute""" self.counter.set_value(self.counter.value + 1)
class RunnableController(ManagerController): """RunnableDevice implementer that also exposes GUI for child parts""" # The stateMachine that this controller implements stateMachine = sm() Validate = Hook() """Called at validate() to check parameters are valid Args: task (Task): The task used to perform operations on child blocks part_info (dict): {part_name: [Info]} returned from ReportStatus params (Map): Any configuration parameters asked for by part validate() method_takes() decorator Returns: [`ParameterTweakInfo`] - any parameters tweaks that have occurred to make them compatible with this part. If any are returned, Validate will be re-run with the modified parameters. """ ReportStatus = Hook() """Called before Validate, Configure, PostRunReady and Seek hooks to report the current configuration of all parts Args: task (Task): The task used to perform operations on child blocks Returns: [`Info`] - any configuration Info objects relevant to other parts """ Configure = Hook() """Called at configure() to configure child block for a run Args: task (Task): The task used to perform operations on child blocks completed_steps (int): Number of steps already completed steps_to_do (int): Number of steps we should configure for part_info (dict): {part_name: [Info]} returned from ReportStatus params (Map): Any configuration parameters asked for by part configure() method_takes() decorator Returns: [`Info`] - any Info objects that need to be passed to other parts for storing in attributes """ PostConfigure = Hook() """Called at the end of configure() to store configuration info calculated in the Configure hook Args: task (Task): The task used to perform operations on child blocks part_info (dict): {part_name: [Info]} returned from Configure hook """ Run = Hook() """Called at run() to start the configured steps running Args: task (Task): The task used to perform operations on child blocks update_completed_steps (callable): If part can report progress, this part should call update_completed_steps(completed_steps, self) with the integer step value each time progress is updated """ PostRunReady = Hook() """Called at the end of run() when there are more steps to be run Args: task (Task): The task used to perform operations on child blocks completed_steps (int): Number of steps already completed steps_to_do (int): Number of steps we should configure for part_info (dict): {part_name: [Info]} returned from ReportStatus params (Map): Any configuration parameters asked for by part configure() method_takes() decorator """ PostRunIdle = Hook() """Called at the end of run() when there are no more steps to be run Args: task (Task): The task used to perform operations on child blocks """ Pause = Hook() """Called at pause() to pause the current scan before Seek is called Args: task (Task): The task used to perform operations on child blocks """ Seek = Hook() """Called at seek() or at the end of pause() to reconfigure for a different number of completed_steps Args: task (Task): The task used to perform operations on child blocks completed_steps (int): Number of steps already completed steps_to_do (int): Number of steps we should configure for part_info (dict): {part_name: [Info]} returned from ReportStatus params (Map): Any configuration parameters asked for by part configure() method_takes() decorator """ Resume = Hook() """Called at resume() to continue a paused scan Args: task (Task): The task used to perform operations on child blocks update_completed_steps (callable): If part can report progress, this part should call update_completed_steps(completed_steps, self) with the integer step value each time progress is updated """ Abort = Hook() """Called at abort() to stop the current scan Args: task (Task): The task used to perform operations on child blocks """ # Attributes completed_steps = None configured_steps = None total_steps = None axes_to_move = None # Params passed to configure() configure_params = None # Stored for pause steps_per_run = 0 # Progress reporting dict # {part: completed_steps for that part} progress_reporting = None @method_writeable_in(sm.IDLE) def edit(self): # Override edit to only work from Idle super(RunnableController, self).edit() @method_writeable_in(sm.FAULT, sm.DISABLED, sm.ABORTED, sm.READY) def reset(self): # Override reset to work from aborted and ready too super(RunnableController, self).reset() def create_attributes(self): for data in super(RunnableController, self).create_attributes(): yield data self.completed_steps = NumberMeta( "int32", "Readback of number of scan steps").make_attribute(0) self.completed_steps.meta.set_writeable_in(sm.PAUSED, sm.READY) yield "completedSteps", self.completed_steps, self.set_completed_steps self.configured_steps = NumberMeta( "int32", "Number of steps currently configured").make_attribute(0) yield "configuredSteps", self.configured_steps, None self.total_steps = NumberMeta( "int32", "Readback of number of scan steps" ).make_attribute(0) yield "totalSteps", self.total_steps, None self.axes_to_move = StringArrayMeta( "Default axis names to scan for configure()" ).make_attribute(self.params.axesToMove) self.axes_to_move.meta.set_writeable_in(sm.EDITABLE) yield "axesToMove", self.axes_to_move, self.set_axes_to_move def do_reset(self): super(RunnableController, self).do_reset() self._update_configure_args() self.configured_steps.set_value(0) self.completed_steps.set_value(0) self.total_steps.set_value(0) def go_to_error_state(self, exception): if isinstance(exception, AbortedError): self.log_info("Got AbortedError in %s" % self.state.value) else: super(RunnableController, self).go_to_error_state(exception) def _update_configure_args(self): # Look for all parts that hook into Configure configure_funcs = self.Configure.find_hooked_functions(self.parts) method_metas = [] for part, func_name in configure_funcs.items(): method_metas.append(part.method_metas[func_name]) # Update takes with the things we need default_configure = MethodMeta.from_dict( RunnableController.configure.MethodMeta.to_dict()) default_configure.defaults["axesToMove"] = self.axes_to_move.value method_metas.append(default_configure) # Decorate validate and configure with the sum of its parts self.block["validate"].recreate_from_others(method_metas) self.block["validate"].set_returns(self.block["validate"].takes) self.block["configure"].recreate_from_others(method_metas) def set_axes_to_move(self, value): self.axes_to_move.set_value(value) self._update_configure_args() @method_takes(*configure_args) def validate(self, params, returns): iterations = 10 # Make some tasks just for validate part_tasks = self.create_part_tasks() # Get any status from all parts status_part_info = self.run_hook(self.ReportStatus, part_tasks) while iterations > 0: # Try up to 10 times to get a valid set of parameters iterations -= 1 # Validate the params with all the parts validate_part_info = self.run_hook( self.Validate, part_tasks, status_part_info, **params) tweaks = ParameterTweakInfo.filter_values(validate_part_info) if tweaks: for tweak in tweaks: params[tweak.parameter] = tweak.value self.log_debug( "Tweaking %s to %s", tweak.parameter, tweak.value) else: # Consistent set, just return the params return params raise ValueError("Could not get a consistent set of parameters") @method_takes(*configure_args) @method_writeable_in(sm.IDLE) def configure(self, params): """Configure for a scan""" self.validate(params, params) self.try_stateful_function( sm.CONFIGURING, sm.READY, self.do_configure, params) def do_configure(self, params): # These are the part tasks that abort() and pause() will operate on self.part_tasks = self.create_part_tasks() # Store the params for use in seek() self.configure_params = params # Set the steps attributes that we will do across many run() calls self.total_steps.set_value(params.generator.num) self.completed_steps.set_value(0) self.configured_steps.set_value(0) # TODO: this should come from tne generator self.steps_per_run = self._get_steps_per_run( params.generator, params.axesToMove) # Get any status from all parts part_info = self.run_hook(self.ReportStatus, self.part_tasks) # Use the ProgressReporting classes for ourselves self.progress_reporting = {} # Run the configure command on all parts, passing them info from # ReportStatus. Parts should return any reporting info for PostConfigure completed_steps = 0 steps_to_do = self.steps_per_run part_info = self.run_hook( self.Configure, self.part_tasks, completed_steps, steps_to_do, part_info, **self.configure_params) # Take configuration info and reflect it as attribute updates self.run_hook(self.PostConfigure, self.part_tasks, part_info) # Update the completed and configured steps self.configured_steps.set_value(steps_to_do) def _get_steps_per_run(self, generator, axes_to_move): steps = 1 axes_set = set(axes_to_move) for g in reversed(generator.generators): # If the axes_set is empty then we are done if not axes_set: break # Consume the axes that this generator scans for axis in g.position_units: assert axis in axes_set, \ "Axis %s is not in %s" % (axis, axes_to_move) axes_set.remove(axis) # Now multiply by the dimensions to get the number of steps for dim in g.index_dims: steps *= dim return steps @method_writeable_in(sm.READY) def run(self): """Run an already configured scan""" if self.configured_steps.value < self.total_steps.value: next_state = sm.READY else: next_state = sm.IDLE self.try_stateful_function(sm.RUNNING, next_state, self._call_do_run) def _call_do_run(self): hook = self.Run while True: try: self.do_run(hook) except AbortedError: # Work out if it was an abort or pause state = self.state.value self.log_debug("Do run got AbortedError from %s", state) if state in (sm.SEEKING, sm.PAUSED): # Wait to be restarted task = Task("StateWaiter", self.process) bad_states = [sm.DISABLING, sm.ABORTING, sm.FAULT] try: task.when_matches(self.state, sm.RUNNING, bad_states) except BadValueError: # raise AbortedError so we don't try to transition raise AbortedError() # Restart it hook = self.Resume self.status.set_value("Run resumed") else: # just drop out raise else: return def do_run(self, hook): self.run_hook(hook, self.part_tasks, self.update_completed_steps) self.transition(sm.POSTRUN, "Finishing run") completed_steps = self.configured_steps.value if completed_steps < self.total_steps.value: steps_to_do = self.steps_per_run part_info = self.run_hook(self.ReportStatus, self.part_tasks) self.completed_steps.set_value(completed_steps) self.run_hook( self.PostRunReady, self.part_tasks, completed_steps, steps_to_do, part_info, **self.configure_params) self.configured_steps.set_value(completed_steps + steps_to_do) else: self.run_hook(self.PostRunIdle, self.part_tasks) def update_completed_steps(self, completed_steps, part): # This is run in the child thread, so make sure it is thread safe self.progress_reporting[part] = completed_steps min_completed_steps = min(self.progress_reporting.values()) if min_completed_steps > self.completed_steps.value: self.completed_steps.set_value(min_completed_steps) @method_writeable_in( sm.IDLE, sm.CONFIGURING, sm.READY, sm.RUNNING, sm.POSTRUN, sm.PAUSED, sm.SEEKING) def abort(self): self.try_stateful_function( sm.ABORTING, sm.ABORTED, self.do_abort, self.Abort) def do_abort(self, hook): for task in self.part_tasks.values(): task.stop() self.run_hook(hook, self.create_part_tasks()) for task in self.part_tasks.values(): task.wait() def set_completed_steps(self, completed_steps): params = self.pause.MethodMeta.prepare_input_map( completedSteps=completed_steps) self.pause(params) @method_writeable_in(sm.READY, sm.PAUSED, sm.RUNNING) @method_takes("completedSteps", NumberMeta( "int32", "Step to mark as the last completed step, -1 for current"), -1) def pause(self, params): current_state = self.state.value if params.completedSteps < 0: completed_steps = self.completed_steps.value else: completed_steps = params.completedSteps if current_state == sm.RUNNING: next_state = sm.PAUSED else: next_state = current_state assert completed_steps < self.total_steps.value, \ "Cannot seek to after the end of the scan" self.try_stateful_function( sm.SEEKING, next_state, self.do_pause, completed_steps) def do_pause(self, completed_steps): self.do_abort(self.Pause) in_run_steps = completed_steps % self.steps_per_run steps_to_do = self.steps_per_run - in_run_steps part_info = self.run_hook(self.ReportStatus, self.part_tasks) self.completed_steps.set_value(completed_steps) self.run_hook( self.Seek, self.part_tasks, completed_steps, steps_to_do, part_info, **self.configure_params) self.configured_steps.set_value(completed_steps + steps_to_do) @method_writeable_in(sm.PAUSED) def resume(self): self.transition(sm.RUNNING, "Resuming run")
class RunnableController(ManagerController): """RunnableDevice implementer that also exposes GUI for child parts""" # The stateMachine that this controller implements stateMachine = sm() Validate = Hook() """Called at validate() to check parameters are valid Args: task (Task): The task used to perform operations on child blocks part_info (dict): {part_name: [Info]} returned from ReportStatus params (Map): Any configuration parameters asked for by part validate() method_takes() decorator Returns: [`ParameterTweakInfo`] - any parameters tweaks that have occurred to make them compatible with this part. If any are returned, Validate will be re-run with the modified parameters. """ ReportStatus = Hook() """Called before Validate, Configure, PostRunReady and Seek hooks to report the current configuration of all parts Args: task (Task): The task used to perform operations on child blocks Returns: [`Info`] - any configuration Info objects relevant to other parts """ Configure = Hook() """Called at configure() to configure child block for a run Args: task (Task): The task used to perform operations on child blocks completed_steps (int): Number of steps already completed steps_to_do (int): Number of steps we should configure for part_info (dict): {part_name: [Info]} returned from ReportStatus params (Map): Any configuration parameters asked for by part configure() method_takes() decorator Returns: [`Info`] - any Info objects that need to be passed to other parts for storing in attributes """ PostConfigure = Hook() """Called at the end of configure() to store configuration info calculated in the Configure hook Args: task (Task): The task used to perform operations on child blocks part_info (dict): {part_name: [Info]} returned from Configure hook """ Run = Hook() """Called at run() to start the configured steps running Args: task (Task): The task used to perform operations on child blocks update_completed_steps (callable): If part can report progress, this part should call update_completed_steps(completed_steps, self) with the integer step value each time progress is updated """ PostRunReady = Hook() """Called at the end of run() when there are more steps to be run Args: task (Task): The task used to perform operations on child blocks completed_steps (int): Number of steps already completed steps_to_do (int): Number of steps we should configure for part_info (dict): {part_name: [Info]} returned from ReportStatus params (Map): Any configuration parameters asked for by part configure() method_takes() decorator """ PostRunIdle = Hook() """Called at the end of run() when there are no more steps to be run Args: task (Task): The task used to perform operations on child blocks """ Pause = Hook() """Called at pause() to pause the current scan before Seek is called Args: task (Task): The task used to perform operations on child blocks """ Seek = Hook() """Called at seek() or at the end of pause() to reconfigure for a different number of completed_steps Args: task (Task): The task used to perform operations on child blocks completed_steps (int): Number of steps already completed steps_to_do (int): Number of steps we should configure for part_info (dict): {part_name: [Info]} returned from ReportStatus params (Map): Any configuration parameters asked for by part configure() method_takes() decorator """ Resume = Hook() """Called at resume() to continue a paused scan Args: task (Task): The task used to perform operations on child blocks update_completed_steps (callable): If part can report progress, this part should call update_completed_steps(completed_steps, self) with the integer step value each time progress is updated """ Abort = Hook() """Called at abort() to stop the current scan Args: task (Task): The task used to perform operations on child blocks """ # Attributes completed_steps = None configured_steps = None total_steps = None axes_to_move = None # Params passed to configure() configure_params = None # Stored for pause steps_per_run = 0 # Progress reporting dict # {part: completed_steps for that part} progress_reporting = None @method_writeable_in(sm.IDLE) def edit(self): # Override edit to only work from Idle super(RunnableController, self).edit() @method_writeable_in(sm.FAULT, sm.DISABLED, sm.ABORTED, sm.READY) def reset(self): # Override reset to work from aborted and ready too super(RunnableController, self).reset() def create_attributes(self): for data in super(RunnableController, self).create_attributes(): yield data self.completed_steps = NumberMeta( "int32", "Readback of number of scan steps").make_attribute(0) self.completed_steps.meta.set_writeable_in(sm.PAUSED, sm.READY) yield "completedSteps", self.completed_steps, self.set_completed_steps self.configured_steps = NumberMeta( "int32", "Number of steps currently configured").make_attribute(0) yield "configuredSteps", self.configured_steps, None self.total_steps = NumberMeta( "int32", "Readback of number of scan steps").make_attribute(0) yield "totalSteps", self.total_steps, None self.axes_to_move = StringArrayMeta( "Default axis names to scan for configure()").make_attribute( self.params.axesToMove) self.axes_to_move.meta.set_writeable_in(sm.EDITABLE) yield "axesToMove", self.axes_to_move, self.set_axes_to_move def do_reset(self): super(RunnableController, self).do_reset() self._update_configure_args() self.configured_steps.set_value(0) self.completed_steps.set_value(0) self.total_steps.set_value(0) def go_to_error_state(self, exception): if isinstance(exception, AbortedError): self.log_info("Got AbortedError in %s" % self.state.value) else: super(RunnableController, self).go_to_error_state(exception) def _update_configure_args(self): # Look for all parts that hook into Configure configure_funcs = self.Configure.find_hooked_functions(self.parts) method_metas = [] for part, func_name in configure_funcs.items(): method_metas.append(part.method_metas[func_name]) # Update takes with the things we need default_configure = MethodMeta.from_dict( RunnableController.configure.MethodMeta.to_dict()) default_configure.defaults["axesToMove"] = self.axes_to_move.value method_metas.append(default_configure) # Decorate validate and configure with the sum of its parts self.block["validate"].recreate_from_others(method_metas) self.block["validate"].set_returns(self.block["validate"].takes) self.block["configure"].recreate_from_others(method_metas) def set_axes_to_move(self, value): self.axes_to_move.set_value(value) self._update_configure_args() @method_takes(*configure_args) def validate(self, params, returns): iterations = 10 # Make some tasks just for validate part_tasks = self.create_part_tasks() # Get any status from all parts status_part_info = self.run_hook(self.ReportStatus, part_tasks) while iterations > 0: # Try up to 10 times to get a valid set of parameters iterations -= 1 # Validate the params with all the parts validate_part_info = self.run_hook(self.Validate, part_tasks, status_part_info, **params) tweaks = ParameterTweakInfo.filter_values(validate_part_info) if tweaks: for tweak in tweaks: params[tweak.parameter] = tweak.value self.log_debug("Tweaking %s to %s", tweak.parameter, tweak.value) else: # Consistent set, just return the params return params raise ValueError("Could not get a consistent set of parameters") @method_takes(*configure_args) @method_writeable_in(sm.IDLE) def configure(self, params): """Configure for a scan""" self.validate(params, params) self.try_stateful_function(sm.CONFIGURING, sm.READY, self.do_configure, params) def do_configure(self, params): # These are the part tasks that abort() and pause() will operate on self.part_tasks = self.create_part_tasks() # Load the saved settings first self.run_hook(self.Load, self.part_tasks, self.load_structure) # Store the params for use in seek() self.configure_params = params # This will calculate what we need from the generator, possibly a long # call params.generator.prepare() # Set the steps attributes that we will do across many run() calls self.total_steps.set_value(params.generator.size) self.completed_steps.set_value(0) self.configured_steps.set_value(0) # TODO: We can be cleverer about this and support a different number # of steps per run for each run by examining the generator structure self.steps_per_run = self._get_steps_per_run(params.generator, params.axesToMove) # Get any status from all parts part_info = self.run_hook(self.ReportStatus, self.part_tasks) # Use the ProgressReporting classes for ourselves self.progress_reporting = {} # Run the configure command on all parts, passing them info from # ReportStatus. Parts should return any reporting info for PostConfigure completed_steps = 0 steps_to_do = self.steps_per_run part_info = self.run_hook(self.Configure, self.part_tasks, completed_steps, steps_to_do, part_info, **self.configure_params) # Take configuration info and reflect it as attribute updates self.run_hook(self.PostConfigure, self.part_tasks, part_info) # Update the completed and configured steps self.configured_steps.set_value(steps_to_do) def _get_steps_per_run(self, generator, axes_to_move): steps = 1 axes_set = set(axes_to_move) for dim in reversed(generator.dimensions): # If the axes_set is empty then we are done if not axes_set: break # Consume the axes that this generator scans for axis in dim.axes: assert axis in axes_set, \ "Axis %s is not in %s" % (axis, axes_to_move) axes_set.remove(axis) # Now multiply by the dimensions to get the number of steps steps *= dim.size return steps @method_writeable_in(sm.READY) def run(self): """Run an already configured scan""" if self.configured_steps.value < self.total_steps.value: next_state = sm.READY else: next_state = sm.IDLE self.try_stateful_function(sm.RUNNING, next_state, self._call_do_run) def _call_do_run(self): hook = self.Run while True: try: self.do_run(hook) except AbortedError: # Work out if it was an abort or pause state = self.state.value self.log_debug("Do run got AbortedError from %s", state) if state in (sm.SEEKING, sm.PAUSED): # Wait to be restarted task = Task("StateWaiter", self.process) bad_states = [sm.DISABLING, sm.ABORTING, sm.FAULT] try: task.when_matches(self.state, sm.RUNNING, bad_states) except BadValueError: # raise AbortedError so we don't try to transition raise AbortedError() # Restart it hook = self.Resume self.status.set_value("Run resumed") else: # just drop out raise else: return def do_run(self, hook): self.run_hook(hook, self.part_tasks, self.update_completed_steps) self.transition(sm.POSTRUN, "Finishing run") completed_steps = self.configured_steps.value if completed_steps < self.total_steps.value: steps_to_do = self.steps_per_run part_info = self.run_hook(self.ReportStatus, self.part_tasks) self.completed_steps.set_value(completed_steps) self.run_hook(self.PostRunReady, self.part_tasks, completed_steps, steps_to_do, part_info, **self.configure_params) self.configured_steps.set_value(completed_steps + steps_to_do) else: self.run_hook(self.PostRunIdle, self.part_tasks) def update_completed_steps(self, completed_steps, part): # This is run in the child thread, so make sure it is thread safe self.progress_reporting[part] = completed_steps min_completed_steps = min(self.progress_reporting.values()) if min_completed_steps > self.completed_steps.value: self.completed_steps.set_value(min_completed_steps) @method_writeable_in(sm.IDLE, sm.CONFIGURING, sm.READY, sm.RUNNING, sm.POSTRUN, sm.PAUSED, sm.SEEKING) def abort(self): self.try_stateful_function(sm.ABORTING, sm.ABORTED, self.do_abort, self.Abort) def do_abort(self, hook): for task in self.part_tasks.values(): task.stop() self.run_hook(hook, self.create_part_tasks()) for task in self.part_tasks.values(): task.wait() def set_completed_steps(self, completed_steps): params = self.pause.MethodMeta.prepare_input_map( completedSteps=completed_steps) self.pause(params) @method_writeable_in(sm.READY, sm.PAUSED, sm.RUNNING) @method_takes( "completedSteps", NumberMeta("int32", "Step to mark as the last completed step, -1 for current"), -1) def pause(self, params): current_state = self.state.value if params.completedSteps < 0: completed_steps = self.completed_steps.value else: completed_steps = params.completedSteps if current_state == sm.RUNNING: next_state = sm.PAUSED else: next_state = current_state assert completed_steps < self.total_steps.value, \ "Cannot seek to after the end of the scan" self.try_stateful_function(sm.SEEKING, next_state, self.do_pause, completed_steps) def do_pause(self, completed_steps): self.do_abort(self.Pause) in_run_steps = completed_steps % self.steps_per_run steps_to_do = self.steps_per_run - in_run_steps part_info = self.run_hook(self.ReportStatus, self.part_tasks) self.completed_steps.set_value(completed_steps) self.run_hook(self.Seek, self.part_tasks, completed_steps, steps_to_do, part_info, **self.configure_params) self.configured_steps.set_value(completed_steps + steps_to_do) @method_writeable_in(sm.PAUSED) def resume(self): self.transition(sm.RUNNING, "Resuming run")
class ManagerController(DefaultController): """RunnableDevice implementer that also exposes GUI for child parts""" # hooks Report = Hook() Validate = Hook() Configuring = Hook() PreRun = Hook() Running = Hook() PostRun = Hook() Aborting = Hook() UpdateLayout = Hook() ListOutports = Hook() # default attributes totalSteps = None layout = None # Params passed to configure() configure_params = None def create_attributes(self): self.totalSteps = NumberMeta( "int32", "Readback of number of scan steps").make_attribute(0) yield "totalSteps", self.totalSteps, None self.layout = layout_table_meta.make_attribute() yield "layout", self.layout, self.set_layout def do_reset(self): super(ManagerController, self).do_reset() self.set_layout(Table(layout_table_meta)) def set_layout(self, value): outport_table = self.run_hook(self.ListOutports, self.create_part_tasks()) layout_table = self.run_hook(self.UpdateLayout, self.create_part_tasks(), layout_table=value, outport_table=outport_table) self.layout.set_value(layout_table) def something_create_methods(self): # Look for all parts that hook into the validate method validate_funcs = self.Validating.find_hooked_functions(self.parts) takes_elements = OrderedDict() defaults = OrderedDict() for part_name, func in validate_funcs.items(): self.log_debug("Adding validating parameters from %s", part_name) takes_elements.update(func.MethodMeta.takes.to_dict()) defaults.update(func.MethodMeta.defaults) takes = ElementMap(takes_elements) # Decorate validate and configure with the sum of its parts # No need to copy as the superclass _set_block_children does this self.validate.MethodMeta.set_takes(takes) self.validate.MethodMeta.set_returns(takes) self.validate.MethodMeta.set_defaults(defaults) self.configure.MethodMeta.set_takes(takes) self.validate.MethodMeta.set_defaults(defaults) return super(ManagerController, self).create_methods() @method_takes(*configure_args) @method_returns(*configure_args) def validate(self, params, _): self.do_validate(params) return params def do_validate(self, params): raise NotImplementedError() @method_only_in(sm.IDLE) @method_takes(*configure_args) def configure(self, params): try: # Transition first so no-one else can run configure() self.transition(sm.CONFIGURING, "Configuring", create_tasks=True) # Store the params and set attributes self.configure_params = params self.totalSteps.set_value(params.generator.num) self.block["completedSteps"].set_value(0) # Do the actual configure self.do_configure() self.transition(sm.READY, "Done configuring") except Exception as e: # pylint:disable=broad-except self.log_exception("Fault occurred while Configuring") self.transition(sm.FAULT, str(e)) raise def do_configure(self, start_step=0): # Ask all parts to report relevant info and pass results to anyone # who cares info_table = self.run_hook(self.Report, self.part_tasks) # Pass results to anyone who cares self.run_hook(self.Configuring, self.part_tasks, info_table=info_table, start_step=start_step, **self.configure_params) @method_only_in(sm.READY) def run(self): try: self.transition(sm.PRERUN, "Preparing for run") self._call_do_run() if self.block["completedSteps"].value < self.totalSteps.value: next_state = sm.READY else: next_state = sm.IDLE self.transition(next_state, "Run finished") except StopIteration: self.log_warning("Run aborted") raise except Exception as e: # pylint:disable=broad-except self.log_exception("Fault occurred while Running") self.transition(sm.FAULT, str(e)) raise def _call_do_run(self): try: self.do_run() except StopIteration: # Work out if it was an abort or pause with self.lock: state = self.state.value self.log_debug("Do run got StopIteration from %s", state) if state in (sm.REWINDING, sm.PAUSED): # Wait to be restarted self.log_debug("Waiting for PreRun") task = Task("StateWaiter", self.process) futures = task.when_matches( self.state, sm.PRERUN, [sm.DISABLING, sm.ABORTING, sm.FAULT]) task.wait_all(futures) # Restart it self.do_run() else: # just drop out self.log_debug("We were aborted") raise def do_run(self): self.run_hook(self.PreRun, self.part_tasks) self.transition(sm.RUNNING, "Waiting for scan to complete") self.run_hook(self.Running, self.part_tasks) self.transition(sm.POSTRUN, "Finishing run") self.run_hook(self.PostRun, self.part_tasks) @method_only_in(sm.IDLE, sm.CONFIGURING, sm.READY, sm.PRERUN, sm.RUNNING, sm.POSTRUN, sm.RESETTING, sm.PAUSED, sm.REWINDING) def abort(self): try: self.transition(sm.ABORTING, "Aborting") self.do_abort() self.transition(sm.ABORTED, "Abort finished") except Exception as e: # pylint:disable=broad-except self.log_exception("Fault occurred while Aborting") self.transition(sm.FAULT, str(e)) raise def do_abort(self): for task in self.part_tasks.values(): task.stop() self.run_hook(self.Aborting, self.create_part_tasks()) for task in self.part_tasks.values(): task.wait() @method_only_in(sm.PRERUN, sm.RUNNING) def pause(self): try: self.transition(sm.REWINDING, "Rewinding") current_index = self.block.completedSteps self.do_abort() self.part_tasks = self.create_part_tasks() self.do_configure(current_index) self.transition(sm.PAUSED, "Pause finished") except Exception as e: # pylint:disable=broad-except self.log_exception("Fault occurred while Pausing") self.transition(sm.FAULT, str(e)) raise @method_only_in(sm.READY, sm.PAUSED) @method_takes("steps", NumberMeta("uint32", "Number of steps to rewind"), REQUIRED) def rewind(self, params): current_index = self.block.completedSteps requested_index = current_index - params.steps assert requested_index >= 0, \ "Cannot retrace to before the start of the scan" try: self.transition(sm.REWINDING, "Rewinding") self.block["completedSteps"].set_value(requested_index) self.do_configure(requested_index) self.transition(sm.PAUSED, "Rewind finished") except Exception as e: # pylint:disable=broad-except self.log_exception("Fault occurred while Rewinding") self.transition(sm.FAULT, str(e)) raise @method_only_in(sm.PAUSED) def resume(self): self.transition(sm.PRERUN, "Resuming run")