class TestAttribute(unittest.TestCase): def setUp(self): self.meta = StringMeta() def test_init(self): a = self.meta.make_attribute() self.assertIs(self.meta, a.meta) self.assertEquals(a.value, "") self.assertEquals("epics:nt/NTScalar:1.0", a.typeid) def test_set_value(self): value = "test_value" a = self.meta.make_attribute() a.process = Mock() a.set_value(value) self.assertEquals(a.value, value) a.process.report_changes.assert_called_once_with([['value'], value]) def test_handle_request(self): a = self.meta.make_attribute() request = Put(endpoint=["1", "2", "value"], value=Mock()) put_function = Mock() a.handle_request(request, put_function) put_function.assert_called_once_with(self.meta, request.value)
def takes_with_default_meta(meta_cls, *meta_args): default_args = meta_args + ( "Default value for parameter. If not specified, parameter is required", ) return method_takes( "name", StringMeta("Specify that this class will take a parameter name"), REQUIRED, "description", StringMeta("Description of this parameter"), REQUIRED, "default", meta_cls(*default_args), OPTIONAL)
def test_put_root_update_response(self): attr1 = StringMeta("dummy").make_attribute() attr2 = StringMeta("dummy2").make_attribute() new_block_structure = OrderedDict(typeid='malcolm:core/Block:1.0') new_block_structure["attr1"] = attr1.to_dict() new_block_structure["attr2"] = attr2.to_dict() response = MagicMock( id=self.cc.BLOCK_ID, changes=[[[], new_block_structure]]) self.cc.put(response) self.assertEqual(self.b.to_dict(), new_block_structure)
def test_instantiate(self): @method_takes("desc", StringMeta("description"), REQUIRED, "foo", StringMeta("optional thing"), "thing") def f(extra, params): return extra, 2, params.desc, params.foo ca = Mock(CAPart=f) parts = Mock(ca=ca) section = Section("ca.CAPart", dict(desc="my name")) result = section.instantiate({}, parts, "extra") self.assertEqual(result, ("extra", 2, "my name", "thing"))
class DummyPart1(object): @DummyController.Configuring def do_thing(self, task): pass @DummyController.Running @method_returns("foo", StringMeta("Value of foo"), REQUIRED, "bar", StringMeta("Value of bar"), REQUIRED) def do_the_other_thing(self, task, returns): returns.foo = "foo1" returns.bar = "bar2" return returns
def test_takes_given_optional(self): @method_takes("hello", StringMeta(), OPTIONAL) def say_hello(params): """Say hello""" print("Hello" + params.name) itakes = MapMeta() itakes.set_elements(ElementMap(OrderedDict(hello=StringMeta()))) self.assertEqual(say_hello.MethodMeta.takes.to_dict(), itakes.to_dict()) self.assertEqual(say_hello.MethodMeta.returns.to_dict(), MapMeta().to_dict()) self.assertEqual(say_hello.MethodMeta.defaults, {})
def test_returns_given_valid_sets(self): @method_returns("hello", StringMeta(), REQUIRED) def say_hello(ret): """Say hello""" ret.hello = "Hello" return ret ireturns = MapMeta() ireturns.set_elements(ElementMap(OrderedDict(hello=StringMeta()))) ireturns.set_required(["hello"]) self.assertEqual(say_hello.MethodMeta.takes.to_dict(), MapMeta().to_dict()) self.assertEqual(say_hello.MethodMeta.returns.to_dict(), ireturns.to_dict()) self.assertEqual(say_hello.MethodMeta.defaults, {})
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
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 setUp(self): self.callback_result = 0 self.callback_value = '' meta = StringMeta("meta for unit tests") self.proc = MagicMock(q=queue.Queue()) self.proc.create_queue = MagicMock(side_effect=queue.Queue) self.block = Block() self.block.set_process_path(self.proc, ("testBlock",)) self.attr = meta.make_attribute() self.attr2 = meta.make_attribute() self.method = MethodMeta("method for unit tests") self.method.returns.set_elements(ElementMap(dict(ret=StringMeta()))) self.method2 = MethodMeta("method for unit tests") self.block.replace_endpoints( dict(testFunc=self.method, testFunc2=self.method2, testAttr=self.attr, testAttr2=self.attr2)) self.bad_called_back = False
def test_returns_not_given_req_or_opt_raises(self): with self.assertRaises(AssertionError): @method_returns("hello", StringMeta(), "A default") def say_hello(ret): """Say hello""" ret.hello = "Hello" return ret
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 setUp(self): self.callback_result = 0 self.callback_value = '' meta = StringMeta("meta for unit tests") self.proc = MagicMock(q=queue.Queue()) self.proc.create_queue = MagicMock(side_effect=queue.Queue) self.block = Block() self.block.set_process_path(self.proc, ("testBlock", )) self.attr = meta.make_attribute() self.attr2 = meta.make_attribute() self.method = MethodMeta("method for unit tests") self.method.returns.set_elements(ElementMap(dict(ret=StringMeta()))) self.method2 = MethodMeta("method for unit tests") self.block.replace_endpoints( dict(testFunc=self.method, testFunc2=self.method2, testAttr=self.attr, testAttr2=self.attr2)) self.bad_called_back = False
def capart_takes(*args): args = ( "name", StringMeta("name of the created attribute"), REQUIRED, "description", StringMeta("desc of created attribute"), REQUIRED, "pv", StringMeta("full pv of demand and default for rbv"), None, "rbv", StringMeta("override for rbv"), None, "rbv_suff", StringMeta("set rbv ro pv + rbv_suff"), None, ) + args return method_takes(*args)
def test_method_also_takes(self): @method_takes("hello", StringMeta(), REQUIRED, "hello2", BooleanMeta(), False) class Thing(object): pass @method_also_takes("world", BooleanMeta(), REQUIRED, "hello2", BooleanMeta(), True, "default", StringMeta(), "nothing") class Thing2(Thing): pass # Check original hasn't been modified itakes = MapMeta() elements = OrderedDict() elements["hello"] = StringMeta() elements["hello2"] = BooleanMeta() itakes.set_elements(ElementMap(elements)) itakes.set_required(["hello"]) defaults = OrderedDict() defaults["hello2"] = False self.assertEqual(Thing.MethodMeta.takes.to_dict(), itakes.to_dict()) self.assertEqual(Thing.MethodMeta.returns.to_dict(), MapMeta().to_dict()) self.assertEqual(Thing.MethodMeta.defaults, defaults) # Check new one overrides/improves on original itakes = MapMeta() elements = OrderedDict() elements["hello"] = StringMeta() elements["hello2"] = BooleanMeta() elements["world"] = BooleanMeta() elements["default"] = StringMeta() itakes.set_elements(ElementMap(elements)) itakes.set_required(["hello", "world"]) defaults = OrderedDict() defaults["hello2"] = True defaults["default"] = "nothing" self.assertEqual(Thing2.MethodMeta.takes.to_dict(), itakes.to_dict()) self.assertEqual(Thing2.MethodMeta.returns.to_dict(), MapMeta().to_dict()) self.assertEqual(Thing2.MethodMeta.defaults, defaults)
def setUp(self): self.serialized = OrderedDict() self.serialized["typeid"] = "malcolm:core/MethodMeta:1.0" self.takes = MapMeta() self.takes.set_elements(ElementMap({"in_attr": StringMeta("desc")})) self.serialized["takes"] = self.takes.to_dict() self.serialized["defaults"] = OrderedDict({"in_attr": "default"}) self.serialized["description"] = "test_description" self.serialized["tags"] = () self.serialized["writeable"] = True self.serialized["label"] = "" self.serialized["returns"] = MapMeta().to_dict()
def _create_default_attributes(self): # Add the state, status and busy attributes self.state = ChoiceMeta("State of Block", self.stateMachine.possible_states, label="State").make_attribute() yield "state", self.state, None self.status = StringMeta("Status of Block", label="Status").make_attribute() yield "status", self.status, None self.busy = BooleanMeta("Whether Block busy or not", label="Busy").make_attribute() yield "busy", self.busy, 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 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 TestValidate(unittest.TestCase): def setUp(self): self.string_meta = StringMeta("test string description") def test_given_value_str_then_return(self): response = self.string_meta.validate("TestValue") self.assertEqual("TestValue", response) def test_given_value_int_then_cast_and_return(self): response = self.string_meta.validate(15) self.assertEqual("15", response) def test_given_value_float_then_cast_and_return(self): response = self.string_meta.validate(12.8) self.assertEqual("12.8", response) def test_given_value_None_then_return(self): response = self.string_meta.validate(None) self.assertEqual("", response)
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 test_put_root_update_response(self): attr1 = StringMeta("dummy").make_attribute() attr2 = StringMeta("dummy2").make_attribute() new_block_structure = OrderedDict(typeid='malcolm:core/Block:1.0') new_block_structure["attr1"] = attr1.to_dict() new_block_structure["attr2"] = attr2.to_dict() response = MagicMock(id=self.cc.BLOCK_ID, changes=[[[], new_block_structure]]) self.cc.put(response) self.assertEqual(self.b.to_dict(), new_block_structure)
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): """Method that should provide Attribute instances for Block Yields: tuple: (string name, Attribute, callable put_function). """ # Add the state, status and busy attributes self.state = ChoiceMeta("State of Block", self.stateMachine.possible_states, label="State").make_attribute() yield "state", self.state, None self.status = StringMeta("Status of Block", label="Status").make_attribute() yield "status", self.status, None self.busy = BooleanMeta("Whether Block busy or not", label="Busy").make_attribute() yield "busy", self.busy, None
def create_attributes(self): for data in super(ManagerController, self).create_attributes(): yield data # Make a table for the layout info we need columns = OrderedDict() columns["name"] = StringArrayMeta("Name of layout part") columns["mri"] = StringArrayMeta("Malcolm full name of child block") columns["x"] = NumberArrayMeta("float64", "X Coordinate of child block") columns["y"] = NumberArrayMeta("float64", "Y Coordinate of child block") columns["visible"] = BooleanArrayMeta("Whether child block is visible") layout_table_meta = TableMeta("Layout of child blocks", columns=columns) layout_table_meta.set_writeable_in(sm.EDITABLE) self.layout = layout_table_meta.make_attribute() yield "layout", self.layout, self.set_layout self.layout_name = StringMeta( "Saved layout name to load").make_attribute() self.layout_name.meta.set_writeable_in(sm.EDITABLE) yield "layoutName", self.layout_name, self.load_layout
def test_equals_maps(self): self.meta.to_dict = MagicMock() m1 = Map(self.meta, {"a":"test"}) m2 = Map(self.meta, {"a":"test2"}) self.assertFalse(m1 == m2) self.assertTrue(m1 != m2) m2.a = "test" self.assertTrue(m1 == m2) self.assertFalse(m1 != m2) m2 = Map(self.meta, {"a":"test", "b":"test"}) self.assertFalse(m1 == m2) m1["b"] = "test" self.assertTrue(m1 == m2) s = StringMeta(description="a string") meta2 = Meta() meta2.elements = {"a":s, "b":s} meta2.required = ["a"] meta2.to_dict = self.meta.to_dict m2 = Map(meta2, {"a":"test", "b":"test"}) self.assertTrue(m1 == m2)
class StatsPluginPart(ChildPart): @RunnableController.ReportStatus def report_info(self, _): return [ CalculatedNDAttributeDatasetInfo(name="sum", attr="StatsTotal") ] def _make_attributes_xml(self): # Make a root element with an NXEntry root_el = ET.Element("Attributes") ET.SubElement( root_el, "Attribute", addr="0", datatype="DOUBLE", type="PARAM", description="Sum of the array", name="StatsTotal", source="TOTAL", ) xml = et_to_string(root_el) return xml @RunnableController.Configure @method_takes("filePath", StringMeta("File path to write data to"), REQUIRED) def configure(self, task, completed_steps, steps_to_do, part_info, params): file_dir, filename = params.filePath.rsplit(os.sep, 1) fs = task.put_many_async( self.child, dict(enableCallbacks=True, computeStatistics=True)) xml = self._make_attributes_xml() attributes_filename = os.path.join( file_dir, "%s-attributes.xml" % self.params.mri) open(attributes_filename, "w").write(xml) fs += task.put_async(self.child["attributesFile"], attributes_filename) task.wait_all(fs)
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
class ManagerController(DefaultController): """RunnableDevice implementer that also exposes GUI for child parts""" # The stateMachine that this controller implements stateMachine = sm() ReportOutports = Hook() """Called before Layout to get outport info from children Args: task (Task): The task used to perform operations on child blocks Returns: [`OutportInfo`] - the type and value of each outport of the child """ Layout = Hook() """Called when layout table set and at init to update child layout Args: task (Task): The task used to perform operations on child blocks part_info (dict): {part_name: [Info]} returned from Layout hook layout_table (Table): A possibly partial set of changes to the layout table that should be acted on Returns: [`LayoutInfo`] - the child layout resulting from this change """ Load = Hook() """Called at load() or revert() to load child settings from a structure Args: task (Task): The task used to perform operations on child blocks structure (dict): {part_name: part_structure} where part_structure is the return from Save hook """ Save = Hook() """Called at save() to serialize child settings into a dict structure Args: task (Task): The task used to perform operations on child blocks Returns: dict: serialized version of the child that could be loaded from """ # attributes layout = None layout_name = None # {part_name: part_structure} of currently loaded settings load_structure = None def create_attributes(self): for data in super(ManagerController, self).create_attributes(): yield data # Make a table for the layout info we need columns = OrderedDict() columns["name"] = StringArrayMeta("Name of layout part") columns["mri"] = StringArrayMeta("Malcolm full name of child block") columns["x"] = NumberArrayMeta("float64", "X Coordinate of child block") columns["y"] = NumberArrayMeta("float64", "Y Coordinate of child block") columns["visible"] = BooleanArrayMeta("Whether child block is visible") layout_table_meta = TableMeta("Layout of child blocks", columns=columns) layout_table_meta.set_writeable_in(sm.EDITABLE) self.layout = layout_table_meta.make_attribute() yield "layout", self.layout, self.set_layout self.layout_name = ChoiceMeta( "Saved layout name to load", []).make_attribute() self.layout_name.meta.set_writeable_in( self.stateMachine.AFTER_RESETTING) yield "layoutName", self.layout_name, self.load_layout assert os.path.isdir(self.params.configDir), \ "%s is not a directory" % self.params.configDir def set_layout(self, value): # If it isn't a table, make it one if not isinstance(value, Table): value = Table(self.layout.meta, value) part_info = self.run_hook(self.ReportOutports, self.create_part_tasks()) part_info = self.run_hook( self.Layout, self.create_part_tasks(), part_info, value) layout_table = Table(self.layout.meta) for name, layout_infos in LayoutInfo.filter_parts(part_info).items(): assert len(layout_infos) == 1, \ "%s returned more than 1 layout infos" % name layout_info = layout_infos[0] row = [name, layout_info.mri, layout_info.x, layout_info.y, layout_info.visible] layout_table.append(row) self.layout.set_value(layout_table) def do_reset(self): super(ManagerController, self).do_reset() # This will trigger all parts to report their layout, making sure the # layout table has a valid value self.set_layout(Table(self.layout.meta)) # List the configDir and add to choices self._set_layout_names() # If we have no load_structure (initial reset) define one if self.load_structure is None: if self.params.defaultConfig: self.load_layout(self.params.defaultConfig) else: self.load_structure = self._save_to_structure() @method_writeable_in(sm.READY) def edit(self): self.transition(sm.EDITABLE, "Layout editable") def go_to_error_state(self, exception): if self.state.value == sm.EDITABLE: # If we got a save or revert exception, don't go to fault self.log_exception("Fault occurred while trying to save/revert") else: super(ManagerController, self).go_to_error_state(exception) @method_writeable_in(sm.EDITABLE) @method_takes( "layoutName", StringMeta( "Name of layout to save to, if different from current layoutName"), None) def save(self, params): self.try_stateful_function( sm.SAVING, self.stateMachine.AFTER_RESETTING, self.do_save, params.layoutName) def do_save(self, layout_name=None): if not layout_name: layout_name = self.layout_name.value structure = self._save_to_structure() text = json_encode(structure, indent=2) filename = self._validated_config_filename(layout_name) open(filename, "w").write(text) self._set_layout_names(layout_name) self.layout_name.set_value(layout_name) self.load_structure = structure def _set_layout_names(self, extra_name=None): names = [] if extra_name: names.append(extra_name) dir_name = self._make_config_dir() for f in os.listdir(dir_name): if os.path.isfile( os.path.join(dir_name, f)) and f.endswith(".json"): names.append(f.split(".json")[0]) self.layout_name.meta.set_choices(names) @method_writeable_in(sm.EDITABLE) def revert(self): self.try_stateful_function( sm.REVERTING, self.stateMachine.AFTER_RESETTING, self.do_revert) def do_revert(self): self._load_from_structure(self.load_structure) def _validated_config_filename(self, name): """Make config dir and return full file path and extension Args: name (str): Filename without dir or extension Returns: str: Full path including extensio """ dir_name = self._make_config_dir() filename = os.path.join(dir_name, name.split(".json")[0] + ".json") return filename def _make_config_dir(self): dir_name = os.path.join(self.params.configDir, self.mri) try: os.mkdir(dir_name) except OSError: # OK if already exists, if not then it will fail on write anyway pass return dir_name def load_layout(self, value): # TODO: race condition if we get 2 loads at once... # Do we need a Loading state? filename = self._validated_config_filename(value) text = open(filename, "r").read() structure = json_decode(text) self._load_from_structure(structure) self.layout_name.set_value(value) def _save_to_structure(self): structure = OrderedDict() structure["layout"] = OrderedDict() for name, x, y, visible in sorted( zip(self.layout.value.name, self.layout.value.x, self.layout.value.y, self.layout.value.visible)): layout_structure = OrderedDict() layout_structure["x"] = x layout_structure["y"] = y layout_structure["visible"] = visible structure["layout"][name] = layout_structure for part_name, part_structure in sorted(self.run_hook( self.Save, self.create_part_tasks()).items()): structure[part_name] = part_structure return structure def _load_from_structure(self, structure): table = Table(self.layout.meta) for part_name, part_structure in structure["layout"].items(): table.append([part_name, "", part_structure["x"], part_structure["y"], part_structure["visible"]]) self.set_layout(table) self.run_hook(self.Load, self.create_part_tasks(), structure)
Args: type (str): Type of the port, e.g. bool or NDArray value (str): Value that will be set when port is selected, e.g. PCOMP1.OUT or DET.STATS """ def __init__(self, type, value): self.type = type self.value = value sm = ManagerStateMachine @method_also_takes( "configDir", StringMeta("Directory to write save/load config to"), REQUIRED, "defaultConfig", StringMeta("Default config to load"), "", ) class ManagerController(DefaultController): """RunnableDevice implementer that also exposes GUI for child parts""" # The stateMachine that this controller implements stateMachine = sm() ReportOutports = Hook() """Called before Layout to get outport info from children Args: task (Task): The task used to perform operations on child blocks Returns: [`OutportInfo`] - the type and value of each outport of the child
def setUp(self): self.string_meta = StringMeta("test string description")
class ManagerController(DefaultController): """RunnableDevice implementer that also exposes GUI for child parts""" # The stateMachine that this controller implements stateMachine = sm() ReportOutports = Hook() """Called before Layout to get outport info from children Args: task (Task): The task used to perform operations on child blocks Returns: [`OutportInfo`] - the type and value of each outport of the child """ Layout = Hook() """Called when layout table set and at init to update child layout Args: task (Task): The task used to perform operations on child blocks part_info (dict): {part_name: [Info]} returned from Layout hook layout_table (Table): A possibly partial set of changes to the layout table that should be acted on Returns: [`LayoutInfo`] - the child layout resulting from this change """ Load = Hook() """Called at load() or revert() to load child settings from a structure Args: task (Task): The task used to perform operations on child blocks structure (dict): {part_name: part_structure} where part_structure is the return from Save hook """ Save = Hook() """Called at save() to serialize child settings into a dict structure Args: task (Task): The task used to perform operations on child blocks structure (dict): {part_name: part_structure} where part_structure is the return from Save hook """ # attributes layout = None layout_name = None # state to revert to revert_structure = None def create_attributes(self): for data in super(ManagerController, self).create_attributes(): yield data # Make a table for the layout info we need columns = OrderedDict() columns["name"] = StringArrayMeta("Name of layout part") columns["mri"] = StringArrayMeta("Malcolm full name of child block") columns["x"] = NumberArrayMeta("float64", "X Coordinate of child block") columns["y"] = NumberArrayMeta("float64", "Y Coordinate of child block") columns["visible"] = BooleanArrayMeta("Whether child block is visible") layout_table_meta = TableMeta("Layout of child blocks", columns=columns) layout_table_meta.set_writeable_in(sm.EDITABLE) self.layout = layout_table_meta.make_attribute() yield "layout", self.layout, self.set_layout self.layout_name = StringMeta( "Saved layout name to load").make_attribute() self.layout_name.meta.set_writeable_in(sm.EDITABLE) yield "layoutName", self.layout_name, self.load_layout def set_layout(self, value): part_info = self.run_hook(self.ReportOutports, self.create_part_tasks()) part_info = self.run_hook( self.Layout, self.create_part_tasks(), part_info, value) layout_table = Table(self.layout.meta) for name, layout_infos in LayoutInfo.filter_parts(part_info).items(): assert len(layout_infos) == 1, \ "%s returned more than 1 layout infos" % name layout_info = layout_infos[0] row = [name, layout_info.mri, layout_info.x, layout_info.y, layout_info.visible] layout_table.append(row) self.layout.set_value(layout_table) def do_reset(self): super(ManagerController, self).do_reset() self.set_layout(Table(self.layout.meta)) @method_writeable_in(sm.EDITABLE) def edit(self): self.try_stateful_function(sm.EDITING, sm.EDITABLE, self.do_edit) def do_edit(self): self.revert_structure = self._save_to_structure() @method_writeable_in(sm.EDITABLE) @method_takes( "layoutName", StringMeta( "Name of layout to save to, if different from current layoutName"), None) def save(self, params): self.try_stateful_function( sm.SAVING, self.stateMachine.AFTER_RESETTING, self.do_save, params.layoutName) def do_save(self, layout_name=None): if layout_name is None or layout_name == '': layout_name = self.layout_name.value structure = self._save_to_structure() filename = "/tmp/" + layout_name + ".json" text = json_encode(structure, indent=2) open(filename, "w").write(text) self.layout_name.set_value(layout_name) @method_writeable_in(sm.EDITABLE) def revert(self): self.try_stateful_function( sm.REVERTING, self.stateMachine.AFTER_RESETTING, self.do_revert) def do_revert(self): self._load_from_structure(self.revert_structure) def load_layout(self, value): # TODO: Look for value in our save file location filename = "/tmp/" + value + ".json" text = open(filename, "r").read() structure = json_decode(text) self._load_from_structure(structure) self.layout_name.set_value(value) def _save_to_structure(self): structure = self.run_hook(self.Save, self.create_part_tasks()) structure["layout"] = self.layout.value.to_dict() return structure def _load_from_structure(self, structure): table = self.layout.meta.validate(structure["layout"]) self.set_layout(table) self.run_hook(self.Load, self.create_part_tasks(), structure)
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 create_meta(self, description, tags): return StringMeta(description=description, tags=tags)
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 setUp(self): self.meta = StringMeta()
def test_to_dict(self): a = StringMeta("desc").make_attribute() a.set_value("some string") self.assertEqual(a.to_dict(), self.serialized)