def make_meta(subtyp, description, tags, writeable=True, labels=None): if subtyp == "enum": if writeable: widget_type = "combo" else: widget_type = "textupdate" tags.append(widget(widget_type)) meta = ChoiceMeta(description, labels, tags) elif subtyp == "bit": if writeable: widget_type = "checkbox" else: widget_type = "led" tags.append(widget(widget_type)) meta = BooleanMeta(description, tags) else: if writeable: widget_type = "textinput" else: widget_type = "textupdate" tags.append(widget(widget_type)) if subtyp == "uint": meta = NumberMeta("uint32", description, tags) elif subtyp == "int": meta = NumberMeta("int32", description, tags) elif subtyp == "scalar": meta = NumberMeta("float64", description, tags) elif subtyp == "lut": meta = StringMeta(description, tags) elif subtyp in ("pos", "relative_pos"): meta = NumberMeta("float64", description, tags) else: raise ValueError("Unknown subtype %r" % subtyp) return meta
def create_attributes(self): for data in super(DetectorDriverPart, self).create_attributes(): yield data meta = NumberMeta("float64", "Time taken to readout detector") self.readout_time = meta.make_attribute(self.params.readoutTime) yield "readoutTime", self.readout_time, self.readout_time.set_value meta = ChoiceMeta("Whether detector is software or hardware triggered", ["Software", "Hardware"]) self.trigger_mode = meta.make_attribute("Hardware") yield "triggerMode", self.trigger_mode, None
def _make_scale_offset(self, field_name): group_tag = self._make_group("outputs") meta = StringMeta("Units for position fields on this block", tags=[group_tag, widget("textinput")]) self._make_field_part(field_name + ".UNITS", meta, writeable=True) meta = NumberMeta("float64", "Scale for block position fields", tags=[group_tag, widget("textinput")]) self._make_field_part(field_name + ".SCALE", meta, writeable=True) meta = NumberMeta("float64", "Offset for block position fields", tags=[group_tag, widget("textinput")]) self._make_field_part(field_name + ".OFFSET", meta, writeable=True)
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)
def create_attributes(self): for data in super(PMACTrajectoryPart, self).create_attributes(): yield data self.min_turnaround = NumberMeta( "float64", "Min time for any gaps between frames").make_attribute( self.params.minTurnaround) yield "minTurnaround", self.min_turnaround, \ self.min_turnaround.set_value
def test_from_dict(self): nm = NumberMeta.from_dict(self.serialized) self.assertEqual(type(nm), NumberMeta) self.assertEquals(nm.description, "desc") self.assertEquals(nm.dtype, "float64") self.assertEqual(nm.tags, []) self.assertFalse(nm.writeable) self.assertEqual(nm.label, "name")
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)
def setUp(self): n = NumberMeta(description='a number') s = StringMeta(description="a string") self.meta = MapMeta() self.meta.set_elements(ElementMap({"a": s, "b": s})) self.meta.set_required(["a"]) self.nmeta = MapMeta() self.nmeta.set_elements(ElementMap({"a": n, "b": n})) self.nmeta.set_required(["a"])
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
class ScanTickerPart(ChildPart): # Generator instance generator = None # Where to start completed_steps = None # How many steps to do steps_to_do = None # When to blow up exception_step = None @RunnableController.Configure @RunnableController.PostRunReady @RunnableController.Seek @method_takes( "generator", PointGeneratorMeta("Generator instance"), REQUIRED, "axesToMove", StringArrayMeta( "List of axes in inner dimension of generator that should be moved" ), REQUIRED, "exceptionStep", NumberMeta("int32", "If >0, raise an exception at the end of this step"), 0) def configure(self, task, completed_steps, steps_to_do, part_info, params): # If we are being asked to move if self.name in params.axesToMove: # Just store the generator and place we need to start self.generator = params.generator self.completed_steps = completed_steps self.steps_to_do = steps_to_do self.exception_step = params.exceptionStep else: # Flag nothing to do self.generator = None @RunnableController.Run @RunnableController.Resume def run(self, task, update_completed_steps): # Start time so everything is relative point_time = time.time() if self.generator: for i in range(self.completed_steps, self.completed_steps + self.steps_to_do): # Get the point we are meant to be scanning point = self.generator.get_point(i) # Update the child counter to be the demand position position = point.positions[self.name] task.put(self.child["counter"], position) # Wait until the next point is due point_time += point.duration wait_time = point_time - time.time() task.sleep(wait_time) # Update the point as being complete update_completed_steps(i + 1, self) # If this is the exception step then blow up assert i +1 != self.exception_step, \ "Raising exception at step %s" % self.exception_step
def test_getattr(self): b = Block() a = NumberMeta("int32").make_attribute() b.replace_endpoints(dict(a=a)) def f(meta, value): a.set_value(value) b.set_writeable_functions(dict(a=f)) b.a = 32 self.assertEqual(b.a, 32)
def _make_time_parts(self, field_name, field_data, writeable): description = field_data.description if writeable: widget_tag = widget("textupdate") group_tag = self._make_group("parameters") else: widget_tag = widget("textinput") group_tag = self._make_group("readbacks") meta = NumberMeta("float64", description, [group_tag, widget_tag]) self._make_field_part(field_name, meta, writeable) meta = ChoiceMeta(description + " time units", ["s", "ms", "us"], tags=[group_tag, widget("combo")]) self._make_field_part(field_name + ".UNITS", meta, writeable=True)
class RawMotorPart(LayoutPart): @ManagerController.Report @method_returns("cs_axis", StringMeta("CS axis (like A, B, I, 0)"), REQUIRED, "cs_port", StringMeta("CS port name"), REQUIRED, "acceleration_time", NumberMeta("float64", "Seconds to velocity"), REQUIRED, "resolution", NumberMeta("float64", "Motor resolution"), REQUIRED, "offset", NumberMeta("float64", "Motor user offset"), REQUIRED, "max_velocity", NumberMeta("float64", "Maximum motor velocity"), REQUIRED, "current_position", NumberMeta("float64", "Current motor position"), REQUIRED) def report_cs_info(self, _, returns): returns.cs_axis = self.child.cs_axis returns.cs_port = self.child.cs_port returns.acceleration_time = self.child.acceleration_time returns.resolution = self.child.resolution returns.offset = self.child.offset returns.max_velocity = self.child.max_velocity returns.current_position = self.child.position return returns
class HelloPart(Part): @method_takes( "name", StringMeta("a name"), REQUIRED, "sleep", NumberMeta("float64", "Time to wait before returning"), 0, ) @method_returns("greeting", StringMeta(description="a greeting"), REQUIRED) def greet(self, parameters, return_map): """Optionally sleep <sleep> seconds, then return a greeting to <name>""" print("Manufacturing greeting...") time.sleep(parameters.sleep) return_map.greeting = "Hello %s" % parameters.name return return_map
def test_to_dict(self): a_mock = MagicMock() s = StringMeta(description="a string") meta = Meta() meta.elements = OrderedDict() meta.elements["b"] = s meta.elements["c"] = s meta.elements["d"] = NumberMeta("int32") meta.elements["e"] = s m = Map(meta, {"b":"test", "d":123, "e":"e"}) expected = OrderedDict() expected["typeid"] = "malcolm:core/Map:1.0" expected["b"] = "test" expected["d"] = 123 expected["e"] = "e" self.assertEquals(expected, m.to_dict())
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
class HelloPart(Part): @method_takes( "name", StringMeta("a name"), REQUIRED, "sleep", NumberMeta("float64", "Time to wait before returning"), 0, ) @method_returns("greeting", StringMeta(description="a greeting"), REQUIRED) def say_hello(self, parameters, return_map): """Says Hello to name Args: parameters(Map): The name of the person to say hello to return_map(Map): Return structure to complete and return Returns: Map: The greeting """ return_map.greeting = "Hello %s" % parameters.name time.sleep(parameters.sleep) return return_map
def create_meta(self, description, tags): return NumberMeta("float64", description=description, tags=tags)
def test_to_dict(self): nm = NumberMeta("float64", "desc", label="name") self.assertEqual(nm.to_dict(), self.serialized)
def test_unsigned_validates(self): nm = NumberMeta("uint32") self.assertEqual(nm.validate("22"), 22) self.assertEqual(nm.validate(-22), 2**32-22)
def test_none_validates(self): nm = NumberMeta("int32") self.assertEquals(0, nm.validate(None))
from malcolm.parts.builtin.attributepart import AttributePart from malcolm.core import method_also_takes from malcolm.core.vmetas import NumberMeta @method_also_takes( "initialValue", NumberMeta("float64", "Initial value of attribute"), 0.0, ) class Float64Part(AttributePart): def get_initial_value(self): return self.params.initialValue def create_meta(self, description, tags): return NumberMeta("float64", description=description, tags=tags)
from malcolm.core import method_takes, REQUIRED from malcolm.core.vmetas import StringMeta, NumberMeta from malcolm.parts.pandabox.pandaboxcontrol import PandABoxControl from malcolm.parts.pandabox.pandaboxpoller import PandABoxPoller @method_takes("mriPrefix", StringMeta("Malcolm resource id prefix for blocks"), REQUIRED, "hostname", StringMeta("Hostname of the box"), "localhost", "port", NumberMeta("uint32", "Port number of the server control"), 8888, "areaDetectorPrefix", StringMeta("Prefix for areaDetector records, if using EPICS"), "") def hardware_collection(process, params): # Connect to the Control port control = PandABoxControl(process, params.hostname, params.port) control.start() # Create a block updater poller = PandABoxPoller(process, control) # Get some information about what is available in this PandABox blocks_data = control.get_blocks_data() blocks = [] parts = [] for block_name, block_data in blocks_data.items(): block_names = [] if block_data.number == 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")
def create_meta(self, description): return NumberMeta("int32", description)
# Crossed zero, put in an explicit zero velocity fraction = velocity_array[-1] / (velocity_array[-1] - v) time_array.append(time_array[-1] + fraction * t) velocity_array.append(0) t -= fraction * t time_array.append(time_array[-1] + t) velocity_array.append(v) # Add on the settle time if self.velocity_settle > 0: time_array.append(time_array[-1] + self.velocity_settle) velocity_array.append(v2) return time_array, velocity_array @method_also_takes( "minTurnaround", NumberMeta( "float64", "Min time for any gaps between frames"), 0.0) class PMACTrajectoryPart(ChildPart): # Axis information stored from validate # {scannable_name: MotorInfo} axis_mapping = None # Lookup of the completed_step value for each point completed_steps_lookup = [] # If we are currently loading then block loading more points loading = False # The last index we have loaded end_index = 0 # Where we should stop loading points steps_up_to = 0 # Stored generator for positions generator = None # Min turnaround time
@asynchronous def get(self, endpoint_str): endpoint = endpoint_str.split("/") request = Get(self, None, endpoint) self.servercomms.on_request(request) # curl --data 'parameters={"name": "me"}' http://localhost:8888/blocks/hello/say_hello @asynchronous def post(self, endpoint_str): endpoint = endpoint_str.split("/") parameters = json.loads(self.get_body_argument("parameters")) request = Post(self, None, endpoint, parameters) self.servercomms.on_request(request) @method_takes("port", NumberMeta("int32", "Port number to run up under"), 8080) class WebsocketServerComms(ServerComms): """A class for communication between browser and server""" def __init__(self, process, params): super(WebsocketServerComms, self).__init__(process) self.set_logger_name("WebsocketServerComms(%(port)d)" % params) MalcWebSocketHandler.servercomms = self MalcBlockHandler.servercomms = self application = Application([(r"/blocks/(.*)", MalcBlockHandler), (r"/ws", MalcWebSocketHandler)]) self.server = HTTPServer(application) self.server.listen(int(params["port"])) self.loop = IOLoop.current() self.add_spawn_function(self.loop.start, self.stop_recv_loop)
class MyCAPart(CAPart): create_meta = MagicMock(return_value=NumberMeta("int32")) get_datatype = MagicMock()
def create_meta(self, description): return NumberMeta("float64", description)
def create_attributes(self): self.counter = NumberMeta("uint32", "A counter").make_attribute(0) yield "counter", self.counter, self.counter.set_value
def create_attributes(self): self.counter = NumberMeta("float64", "A counter").make_attribute() yield "counter", self.counter, self.counter.set_value
def test_int_against_float(self): nm = NumberMeta("float64") self.assertEqual(123, nm.validate(123))
class LayoutPart(Part): # Child block object child = None # {part_name: visible} saying whether part_name is visible part_visible = None # Layout options x = 0 y = 0 visible = False mri = None name = None def store_params(self, params): self.name = params.name self.child = self.process.get_block(params.child) self.mri = params.child self.part_visible = {} @ManagerController.UpdateLayout @method_takes("layout_table", layout_table_meta, REQUIRED, "outport_table", outport_table_meta, REQUIRED) @method_returns("mri", StringMeta("Malcolm full name of child block"), REQUIRED, "x", NumberMeta("float64", "X Co-ordinate of child block"), REQUIRED, "y", NumberMeta("float64", "X Co-ordinate of child block"), REQUIRED, "visible", BooleanMeta("Whether child block is visible"), REQUIRED) def update_layout_table(self, task, params, returns): for i, name in enumerate(params.layout_table.name): _, _, x, y, visible = params.layout_table[i] if name == self.name: if self.visible and not visible: self.sever_inports(task) self.x = x self.y = y self.visible = visible else: was_visible = self.part_visible.get(name, True) if was_visible and not visible: self.sever_outports(task, name, params.outport_table) self.part_visible[name] = visible returns.mri = self.mri returns.x = self.x returns.y = self.y returns.visible = self.visible return returns def _get_flowgraph_ports(self, direction="out"): # {attr_name: port_tag} ports = OrderedDict() for attr_name in self.child.endpoints: attr = self.child[attr_name] if isinstance(attr, Attribute): for tag in attr.meta.tags: if tag.startswith("flowgraph:%sport" % direction): ports[attr] = tag return ports def sever_inports(self, task): inports = self._get_flowgraph_ports("in") futures = [] for attr in inports: futures += task.put_async(attr, attr.meta.choices[0]) task.wait_all(futures) def sever_outports(self, task, name, outport_table): # Find the outports of this part # {outport_value: typ} e.g. "PCOMP.OUT" -> "bit" outports = {} for i, n in enumerate(outport_table.name): if n == name: outports[outport_table.value[i]] = outport_table.type[i] inports = self._get_flowgraph_ports("in") futures = [] for attr, port_tag in inports.items(): typ = port_tag.split(":")[2] if outports.get(attr.value, None) == typ: futures += task.put_async(attr, attr.meta.choices[0]) task.wait_all(futures) @ManagerController.ListOutports @method_returns("type", StringArrayMeta("Type of outport (e.g. bit or pos)"), REQUIRED, "value", StringArrayMeta("Value of outport (e.g. PULSE1.OUT)"), REQUIRED) def list_outports(self, _, returns): outports = self._get_flowgraph_ports("out") types = [] values = [] for port_tag in outports.values(): _, _, typ, name = port_tag.split(":", 4) types.append(typ) values.append(name) returns.type = types returns.value = values return returns
def test_int_against_int(self): nm = NumberMeta("int32") self.assertEqual(123, nm.validate(123))
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")
def test_float_to_int_truncates(self): nm = NumberMeta("int32") self.assertEquals(nm.validate(123.6), 123)
from malcolm.core import Part, method_takes, REQUIRED, MethodMeta from malcolm.core.vmetas import StringMeta, NumberMeta, BooleanMeta from malcolm.controllers.defaultcontroller import DefaultController from malcolm.parts.ca.cothreadimporter import CothreadImporter @method_takes( "name", StringMeta("name of the created method"), REQUIRED, "description", StringMeta("desc of created method"), REQUIRED, "pv", StringMeta("full pv to write to when method called"), REQUIRED, "statusPv", StringMeta("Status pv to see if successful"), None, "goodStatus", StringMeta("Good value for status pv"), "", "value", NumberMeta("int32", "value to write to pv when method called"), 1, "wait", BooleanMeta("Wait for caput callback?"), True) class CAActionPart(Part): method = None def __init__(self, process, params): self.cothread, self.catools = CothreadImporter.get_cothread(process) super(CAActionPart, self).__init__(process, params) def create_methods(self): # MethodMeta instance self.method = MethodMeta(self.params.description) # TODO: set widget tag? yield self.params.name, self.method, self.caput @DefaultController.Reset def connect_pvs(self, _): # make the connection in cothread's thread pvs = [self.params.pv]
def test_float_against_float32(self): nm = NumberMeta("float32") self.assertAlmostEqual(123.456, nm.validate(123.456), places=5)
from tornado import gen from tornado.ioloop import IOLoop from tornado.websocket import websocket_connect from malcolm.core import ClientComms, Request, Subscribe, Response, \ deserialize_object, method_takes from malcolm.core.jsonutils import json_decode, json_encode from malcolm.core.vmetas import StringMeta, NumberMeta @method_takes("hostname", StringMeta("Hostname of malcolm websocket server"), "localhost", "port", NumberMeta("int32", "Port number to run up under"), 8080) class WebsocketClientComms(ClientComms): """A class for a client to communicate with the server""" def __init__(self, process, params): """ Args: process (Process): Process for primitive creation params (Map): Parameters map """ super(WebsocketClientComms, self).__init__(process) self.url = "ws://%(hostname)s:%(port)d/ws" % params self.set_logger_name(self.url) # TODO: Are we starting one or more IOLoops here? self.conn = None self.loop = IOLoop.current() self.loop.add_callback(self.recv_loop) self.add_spawn_function(self.loop.start, self.stop_recv_loop) @gen.coroutine
def test_float_against_float64(self): nm = NumberMeta("float64") self.assertEqual(123.456, nm.validate(123.456))