Exemple #1
0
    def __init__(self, name: parts.APartName, ioc: AIoc) -> None:
        super().__init__(name)

        self.dls_ver_pv = ca.util.CAAttribute(
            StringMeta("IOC version"),
            ca.util.catools.DBR_STRING,
            "",
            ioc + ":DLSVER",
            throw=False,
            callback=self.version_updated,
        )
        self.dir1_pv = ca.util.CAAttribute(
            StringMeta("IOC directory pt1"),
            ca.util.catools.DBR_STRING,
            "",
            ioc + ":APP_DIR1",
            widget=Widget.NONE,
            throw=False,
            callback=self.set_dir1,
        )
        self.dir2_pv = ca.util.CAAttribute(
            StringMeta("IOC directory pt2"),
            ca.util.catools.DBR_STRING,
            "",
            ioc + ":APP_DIR2",
            widget=Widget.NONE,
            throw=False,
            callback=self.set_dir2,
        )

        self.autosave_status_pv = ca.util.CAAttribute(
            StringMeta("IOC Status"),
            ca.util.catools.DBR_STRING,
            "",
            ioc + ":STATUS",
            throw=False,
            callback=self.set_procserv_state,
        )

        self.dir1 = None
        self.dir2 = None
        self.dir = ""

        self.has_procserv = False

        elements = OrderedDict()
        elements["module"] = StringArrayMeta("Module", tags=[Widget.TEXTUPDATE.tag()])
        elements["path"] = StringArrayMeta("Path", tags=[Widget.TEXTUPDATE.tag()])

        self.dependencies = TableMeta(
            "Modules which this IOC depends on",
            tags=[Widget.TABLE.tag()],
            writeable=False,
            elements=elements,
        ).create_attribute_model({"module": [], "path": []})
Exemple #2
0
 def test_to_dict(self):
     elements = OrderedDict()
     elements["foo"] = StringArrayMeta(label="Foo")
     elements["bar"] = StringArrayMeta()
     meta = TableMeta(description="desc", tags=[], writeable=True,
                      label="my label", elements=elements)
     value = meta.table_cls(
         foo=["foo1", "foo2"],
         bar=["bar1", "bar2"])
     o = meta.create_attribute_model(value)
     o.set_ts(self.serialized["timeStamp"])
     assert o.meta.elements["foo"].to_dict() == self.serialized["meta"]["elements"]["foo"]
     assert o.to_dict() == self.serialized
Exemple #3
0
    def setup(self, registrar: PartRegistrar) -> None:
        attr = TableMeta.from_table(
            SequencerTable,
            "Sequencer Table",
            writeable=list(
                SequencerTable.call_types)).create_attribute_model()
        self.table_set = MagicMock(side_effect=attr.set_value)
        registrar.add_attribute_model("table", attr, self.table_set)
        for suff, val in (("a", "INENC1.VAL"), ("b", "INENC2.VAL"), ("c",
                                                                     "ZERO")):
            attr = StringMeta("Input").create_attribute_model(val)
            registrar.add_attribute_model(f"pos{suff}", attr)
        attr = StringMeta("Input").create_attribute_model("ZERO")
        registrar.add_attribute_model("bita", attr)

        attr = BooleanMeta("Active", (), True).create_attribute_model(False)
        registrar.add_attribute_model("active", attr, attr.set_value)

        attr = NumberMeta("int16", "repeats",
                          writeable=True).create_attribute_model(1)
        registrar.add_attribute_model("repeats",
                                      attr,
                                      writeable_func=attr.set_value)

        attr = NumberMeta("int16", "prescale",
                          writeable=True).create_attribute_model(0)
        registrar.add_attribute_model("prescale",
                                      attr,
                                      writeable_func=attr.set_value)
 def _make_table(self, field_name, field_data):
     group = self._make_group("parameters")
     tags = [Widget.TABLE.tag(), group, config_tag()]
     meta = TableMeta(field_data.description, tags, writeable=True)
     part = PandABlocksTablePart(self.client, meta, self.block_name,
                                 field_name)
     self._add_part(field_name, part)
Exemple #5
0
 def _make_table(self, field_name: str, field_data: FieldData) -> None:
     group = self._make_group("parameters")
     tags = [Widget.TABLE.tag(), group, config_tag()]
     meta = TableMeta(field_data.description, tags, writeable=True)
     part = PandATablePart(self.client, meta, self.block_name, field_name)
     self.add_part(part)
     self.field_parts[field_name] = part
Exemple #6
0
 def test_init(self):
     tm = TableMeta("desc")
     assert "desc" == tm.description
     assert "malcolm:core/TableMeta:1.0" == tm.typeid
     assert [] == tm.tags
     assert False is tm.writeable
     assert "" == tm.label
Exemple #7
0
 def setUp(self):
     self.client = Mock()
     fields = OrderedDict()
     fields["NREPEATS"] = TableFieldData(15, 0, "Num Repeats", None, False)
     fields["TRIGGER"] = TableFieldData(19, 16, "Choices", ["A", "b", "CC"],
                                        False)
     fields["POSITION"] = TableFieldData(63, 32, "Position", None, True)
     fields["TIME1"] = TableFieldData(95, 64, "Time Phase A", None, False)
     fields["OUTA1"] = TableFieldData(20, 20, "Out1", None, False)
     fields["TIME2"] = TableFieldData(127, 96, "Time Phase B", None, False)
     fields["OUTA2"] = TableFieldData(26, 26, "Out2", None, False)
     self.client.get_table_fields.return_value = fields
     self.meta = TableMeta("Seq table", writeable=True)
     self.o = PandATablePart(self.client,
                             self.meta,
                             block_name="SEQ1",
                             field_name="TABLE")
 def __init__(self, name):
     # type: (APartName) -> None
     super(DatasetTablePart, self).__init__(name)
     self.datasets = TableMeta.from_table(
         DatasetTable,
         "Datasets produced in HDF file").create_attribute_model()
     self.register_hooked(scanning.hooks.PostConfigureHook,
                          self.post_configure)
 def setup(self, registrar: PartRegistrar) -> None:
     self.datasets = TableMeta.from_table(
         DatasetTable,
         "Datasets produced in HDF file").create_attribute_model()
     registrar.add_attribute_model("datasets", self.datasets)
     # Hooks
     registrar.hook(PostConfigureHook, self.on_post_configure)
     registrar.hook(builtin.hooks.ResetHook, self.on_reset)
Exemple #10
0
 def test_from_dict(self):
     tm = TableMeta.from_dict(self.serialized)
     assert tm.description == "desc"
     assert len(tm.elements) == 1
     assert tm.elements["c1"].to_dict() == self.sam.to_dict()
     assert tm.tags == []
     assert tm.writeable is True
     assert tm.label == "Name"
Exemple #11
0
 def setUp(self):
     tm = TableMeta("desc")
     self.tm = tm
     self.tm.set_elements(dict(c1=StringArrayMeta()))
     self.sam = StringArrayMeta()
     self.serialized = OrderedDict()
     self.serialized["typeid"] = "malcolm:core/TableMeta:1.0"
     self.serialized["description"] = "desc"
     self.serialized["tags"] = []
     self.serialized["writeable"] = True
     self.serialized["label"] = "Name"
     self.serialized["elements"] = dict(c1=self.sam.to_dict())
Exemple #12
0
 def setup(self, registrar: PartRegistrar) -> None:
     self.bits = TableMeta.from_table(
         self.bits_table_cls,
         "Current values and capture status of Bit fields",
         writeable=[
             x for x in self.bits_table_cls.call_types
             if x not in ("name", "value")
         ],
         extra_tags=[config_tag()],
     ).create_attribute_model()
     self.positions = TableMeta.from_table(
         self.positions_table_cls,
         "Current values, scaling, and capture status of Position fields",
         writeable=[
             x for x in self.positions_table_cls.call_types
             if x not in ("name", "value")
         ],
         extra_tags=[config_tag()],
     ).create_attribute_model()
     registrar.add_attribute_model("bits", self.bits, self.set_bits)
     registrar.add_attribute_model("positions", self.positions,
                                   self.set_positions)
Exemple #13
0
 def setUp(self):
     self.client = Mock()
     fields = OrderedDict()
     fields["NREPEATS"] = TableFieldData(7, 0, "Num Repeats", None)
     fields["SWITCH"] = TableFieldData(34, 32, "Choices", ["A", "b", "CC"])
     fields["TRIGGER_MASK"] = TableFieldData(48, 48, "Trigger Mask", None)
     fields["TIME_PH_A"] = TableFieldData(95, 64, "Time Phase A", None)
     self.client.get_table_fields.return_value = fields
     self.meta = TableMeta("Seq table", writeable=True)
     self.o = PandABlocksTablePart(self.client,
                                   self.meta,
                                   block_name="SEQ1",
                                   field_name="TABLE")
 def setup(self, registrar: PartRegistrar) -> None:
     attr = TableMeta.from_table(
         SequencerTable,
         "Sequencer Table",
         writeable=list(
             SequencerTable.call_types)).create_attribute_model()
     self.table_set = MagicMock(side_effect=attr.set_value)
     registrar.add_attribute_model("table", attr, self.table_set)
     for suff, val in (("a", "INENC1.VAL"), ("b", "INENC2.VAL"), ("c",
                                                                  "ZERO")):
         attr = StringMeta("Input").create_attribute_model(val)
         registrar.add_attribute_model("pos%s" % suff, attr)
     attr = StringMeta("Input").create_attribute_model("ZERO")
     registrar.add_attribute_model("bita", attr)
 def __init__(
     self,
     name: APartName,
     mri: AMri,
     soft_trigger_modes: USoftTriggerModes = None,
     multiple_image_mode: AMultipleImageMode = "Multiple",
     main_dataset_useful: AMainDatasetUseful = True,
     runs_on_windows: APartRunsOnWindows = False,
     required_version: AVersionRequirement = None,
     min_acquire_period: AMinAcquirePeriod = 0.0,
 ) -> None:
     super().__init__(name, mri)
     self.required_version = required_version
     self.min_acquire_period = min_acquire_period
     self.soft_trigger_modes = soft_trigger_modes
     self.multiple_image_mode = multiple_image_mode
     self.is_hardware_triggered = True
     self.main_dataset_useful = main_dataset_useful
     self.attributes_filename = ""
     self.extra_attributes = TableMeta.from_table(
         ExtraAttributesTable,
         "Extra attributes to be added to the dataset",
         writeable=[
             "name",
             "pv",
             "description",
             "sourceId",
             "sourceType",
             "dataType",
             "datasetType",
         ],
         extra_tags=[config_tag()],
     ).create_attribute_model()
     self.runs_on_windows = runs_on_windows
     # How long to wait between frame updates before error
     self.frame_timeout = 0.0
     # When arrayCounter gets to here we are done
     self.done_when_reaches = 0
     # CompletedSteps = arrayCounter + self.uniqueid_offset
     self.uniqueid_offset = 0
     # A future that completes when detector start calls back
     self.start_future: Optional[Future] = None
 def __init__(
     self,
     mri: builtin.controllers.AMri,
     hostname: AHostname = "localhost",
     port: APort = 8008,
     connect_timeout: AConnectTimeout = DEFAULT_TIMEOUT,
 ) -> None:
     super().__init__(mri)
     self.hostname = hostname
     self.port = port
     self.connect_timeout = connect_timeout
     self._connected_queue = Queue()
     # {new_id: request}
     self._request_lookup: Dict[int, Request] = {}
     self._next_id = 1
     self._conn: Optional[WebSocketClientConnection] = None
     # Create read-only attribute for the remotely reachable blocks
     self.remote_blocks = TableMeta.from_table(
         BlockTable, "Remotely reachable blocks").create_attribute_model()
     self.field_registry.add_attribute_model("remoteBlocks",
                                             self.remote_blocks)
 def setup(self, registrar: PartRegistrar) -> None:
     pos_table = DatasetPositionsTable(
         name=["COUNTER1.VALUE", "INENC1.VAL", "INENC2.VAL"],
         value=[0.0] * 3,
         units=[""] * 3,
         # NOTE: x inverted from MRES below to simulate inversion of
         # encoder in the geobrick layer
         scale=[1.0, -0.001, 0.001],
         offset=[0.0, 0.0, 0.0],
         capture=[PositionCapture.MIN_MAX_MEAN] * 3,
         datasetName=["I0", "x", "y"],
         datasetType=[
             AttributeDatasetType.MONITOR,
             AttributeDatasetType.POSITION,
             AttributeDatasetType.POSITION,
         ],
     )
     attr = TableMeta.from_table(
         DatasetPositionsTable,
         "Sequencer Table",
         writeable=list(SequencerTable.call_types),
     ).create_attribute_model(pos_table)
     registrar.add_attribute_model("positions", attr)
Exemple #18
0
 def __init__(
     self,
     name: util.APartName,
     description: util.AMetaDescription,
     pv_list: util.APvList = (),
     name_list: util.ANameList = (),
     min_delta: util.AMinDelta = 0.05,
     timeout: util.ATimeout = DEFAULT_TIMEOUT,
     widget: util.AWidget = Widget.PLOT,
     group: util.AGroup = None,
     config: util.AConfig = True,
     display_from_pv: util.AGetLimits = True,
 ) -> None:
     if len(pv_list) != len(name_list):
         raise BadValueError(
             "List of PVs must be same length as list of names!")
     super().__init__(name)
     self.display_from_pv = display_from_pv
     elements = {}
     for name in name_list:
         elements[name] = NumberArrayMeta("float64",
                                          name,
                                          tags=[Widget.TEXTUPDATE.tag()])
     self.name_list = name_list
     self.pv_list = pv_list
     self.caa = util.WaveformTableAttribute(
         TableMeta(description, writeable=False, elements=elements),
         util.catools.DBR_DOUBLE,
         pv_list,
         name_list,
         min_delta,
         timeout,
         widget,
         group,
         config,
         on_connect=self._update_display,
     )
 def __init__(
     self,
     mri: AMri,
     config_dir: AConfigDir,
     template_designs: ATemplateDesigns = "",
     initial_design: AInitialDesign = "",
     use_git: AUseGit = True,
     description: ADescription = "",
 ) -> None:
     super().__init__(mri=mri, description=description)
     assert os.path.isdir(config_dir), "%s is not a directory" % config_dir
     self.config_dir = config_dir
     self.initial_design = initial_design
     self.use_git = use_git
     self.template_designs = template_designs
     self.git_config: Tuple[str, ...]
     if use_git:
         if check_git_version("1.7.2"):
             self.git_email = os.environ["USER"] + "@" + socket.gethostname(
             )
             self.git_name = "Malcolm"
             self.git_config = (
                 "-c",
                 "user.name=%s" % self.git_name,
                 "-c",
                 'user.email="%s"' % self.git_email,
             )
         else:
             self.git_config = ()
     # last saved layout and exports
     self.saved_visibility = None
     self.saved_exports = None
     # ((name, AttributeModel/MethodModel, setter, needs_context))
     self._current_part_fields = ()
     self._subscriptions: List[Subscribe] = []
     self.port_info: Dict[APartName, List[PortInfo]] = {}
     self.part_exportable: Dict[Part, List[AAttributeName]] = {}
     # TODO: turn this into "exported attribute modified"
     self.context_modified: Dict[Part, Set[str]] = {}
     self.part_modified: Dict[Part, PartModifiedInfo] = {}
     # The attributes our part has published
     self.our_config_attributes: Dict[str, AttributeModel] = {}
     # The reportable infos we are listening for
     self.info_registry.add_reportable(PartModifiedInfo,
                                       self.update_modified)
     # Update queue of exportable fields
     self.info_registry.add_reportable(PartExportableInfo,
                                       self.update_exportable)
     # Create a port for ourself
     self.field_registry.add_attribute_model(
         "mri",
         StringMeta(
             "A port for giving our MRI to things that might use us",
             tags=[Port.BLOCK.source_port_tag(self.mri)],
         ).create_attribute_model(self.mri),
     )
     # Create a layout table attribute for setting block positions
     self.layout = TableMeta.from_table(
         LayoutTable,
         "Layout of child blocks",
         Widget.FLOWGRAPH,
         writeable=["x", "y", "visible"],
     ).create_attribute_model()
     self.set_writeable_in(self.layout, ss.READY)
     self.field_registry.add_attribute_model("layout", self.layout,
                                             self.set_layout)
     # Create a design attribute for loading an existing layout
     self.design = ChoiceMeta("Design name to load",
                              tags=[config_tag(),
                                    Widget.COMBO.tag()
                                    ]).create_attribute_model()
     self.field_registry.add_attribute_model("design", self.design,
                                             self.set_design)
     self.set_writeable_in(self.design, ss.READY)
     # Create an export table for mirroring exported fields
     self.exports = TableMeta.from_table(
         ExportTable,
         "Exported fields of child blocks",
         writeable=list(ExportTable.call_types),
     ).create_attribute_model()
     # Overwrite the sources meta to be a ChoiceArrayMeta
     self.exports.meta.elements["source"] = ChoiceArrayMeta(
         "Name of the block.field to export",
         writeable=True,
         tags=[Widget.COMBO.tag()],
     )
     self.set_writeable_in(self.exports, ss.READY)
     self.field_registry.add_attribute_model("exports", self.exports,
                                             self.set_exports)
     # Create read-only indicator for when things are modified
     self.modified = BooleanMeta("Whether the design is modified",
                                 tags=[Widget.LED.tag()
                                       ]).create_attribute_model()
     self.field_registry.add_attribute_model("modified", self.modified)
     # Create the save method
     self.set_writeable_in(self.field_registry.add_method_model(self.save),
                           ss.READY)
Exemple #20
0
class DirParsePart(Part):
    registrar = None
    ioc_prod_root = ""
    dls_version = None

    def __init__(self, name: parts.APartName, ioc: AIoc) -> None:
        super().__init__(name)

        self.dls_ver_pv = ca.util.CAAttribute(
            StringMeta("IOC version"),
            ca.util.catools.DBR_STRING,
            "",
            ioc + ":DLSVER",
            throw=False,
            callback=self.version_updated,
        )
        self.dir1_pv = ca.util.CAAttribute(
            StringMeta("IOC directory pt1"),
            ca.util.catools.DBR_STRING,
            "",
            ioc + ":APP_DIR1",
            widget=Widget.NONE,
            throw=False,
            callback=self.set_dir1,
        )
        self.dir2_pv = ca.util.CAAttribute(
            StringMeta("IOC directory pt2"),
            ca.util.catools.DBR_STRING,
            "",
            ioc + ":APP_DIR2",
            widget=Widget.NONE,
            throw=False,
            callback=self.set_dir2,
        )

        self.autosave_status_pv = ca.util.CAAttribute(
            StringMeta("IOC Status"),
            ca.util.catools.DBR_STRING,
            "",
            ioc + ":STATUS",
            throw=False,
            callback=self.set_procserv_state,
        )

        self.dir1 = None
        self.dir2 = None
        self.dir = ""

        self.has_procserv = False

        elements = OrderedDict()
        elements["module"] = StringArrayMeta("Module", tags=[Widget.TEXTUPDATE.tag()])
        elements["path"] = StringArrayMeta("Path", tags=[Widget.TEXTUPDATE.tag()])

        self.dependencies = TableMeta(
            "Modules which this IOC depends on",
            tags=[Widget.TABLE.tag()],
            writeable=False,
            elements=elements,
        ).create_attribute_model({"module": [], "path": []})

    def setup(self, registrar: PartRegistrar) -> None:
        super().setup(registrar)
        registrar.add_attribute_model("dlsVersion", self.dls_ver_pv.attr)
        registrar.add_attribute_model("dir1", self.dir1_pv.attr)
        registrar.add_attribute_model("dir2", self.dir2_pv.attr)
        registrar.add_attribute_model("autosaveStatus", self.autosave_status_pv.attr)
        registrar.add_attribute_model("dependencies", self.dependencies)

        self.register_hooked(hooks.DisableHook, self.disconnect)
        self.register_hooked((hooks.InitHook, hooks.ResetHook), self.reconnect)

    def reconnect(self):
        self.dls_ver_pv.reconnect()
        self.dir1_pv.reconnect()
        self.dir2_pv.reconnect()
        self.autosave_status_pv.reconnect()

    def disconnect(self):
        self.dls_ver_pv.disconnect()
        self.dir1_pv.disconnect()
        self.dir2_pv.disconnect()
        self.autosave_status_pv.disconnect()

    def set_procserv_state(self, value):
        if value.ok:
            self.has_procserv = True
            self.version_updated(self.dls_version)

    def version_updated(self, value):
        if value is not None and value.ok:
            self.dls_version = value
            if isinstance(value, str):
                if value.lower() == "work" or value.lower() == "other":
                    message = "IOC running from non-prod area"
                    stat = alarm.Alarm(
                        message=message, severity=alarm.AlarmSeverity.MINOR_ALARM
                    )
                    self.registrar.report(infos.HealthInfo(stat))
                else:
                    message = "OK"
                    stat = alarm.Alarm(
                        message=message, severity=alarm.AlarmSeverity.NO_ALARM
                    )
                    self.registrar.report(infos.HealthInfo(stat))

        else:
            if self.has_procserv:
                message = "IOC not running (procServ enabled)"
                stat = alarm.Alarm(
                    message=message, severity=alarm.AlarmSeverity.UNDEFINED_ALARM
                )
                self.registrar.report(infos.HealthInfo(stat))
            else:
                message = "neither IOC nor procServ are running"
                stat = alarm.Alarm(
                    message=message, severity=alarm.AlarmSeverity.INVALID_ALARM
                )
                self.registrar.report(infos.HealthInfo(stat))

    def set_dir1(self, value):
        if value.ok:
            self.dir1 = value
        if self.dir1 is not None and self.dir2 is not None:
            self.dir = self.dir1 + self.dir2
            self.parse_release()

    def set_dir2(self, value):
        if value.ok:
            self.dir2 = value
        if self.dir1 is not None and self.dir2 is not None:
            self.dir = self.dir1 + self.dir2
            self.parse_release()

    def parse_release(self):
        release_file = os.path.join(self.dir, "configure", "RELEASE")
        dependencies = OrderedDict()
        dependency_table = OrderedDict()
        if os.path.isdir(self.dir) and os.path.isfile(release_file):
            with open(release_file, "r") as release:
                dep_list = release.readlines()
                dep_list = [
                    dep.strip("\n") for dep in dep_list if not dep.startswith("#")
                ]
                for dep in dep_list:
                    dep_split = dep.replace(" ", "").split("=")
                    if len(dep_split) == 2:
                        dependencies[dep_split[0]] = dep_split[1]
                dependency_table["module"] = []
                dependency_table["path"] = []
                for k1, v1 in dependencies.items():
                    for k2, v2 in dependencies.items():
                        dependencies[k2] = v2.replace(f"$({k1})", v1)

                for k1, v1 in dependencies.items():
                    dependency_table["module"] += [k1]
                    dependency_table["path"] += [v1]

            if len(dep_list) > 0:
                self.dependencies.set_value(dependency_table)

        else:
            status = alarm.Alarm(
                message="reported IOC directory not found",
                severity=alarm.AlarmSeverity.MINOR_ALARM,
            )
            self.dependencies.set_alarm(status)
Exemple #21
0
 def test_to_dict(self):
     tm = TableMeta("desc")
     tm.set_label("Name")
     tm.set_writeable(True)
     tm.set_elements(dict(c1=self.sam))
     assert tm.to_dict() == self.serialized
Exemple #22
0
class PandABoxTablePartTest(unittest.TestCase):
    def setUp(self):
        self.client = Mock()
        fields = OrderedDict()
        fields["NREPEATS"] = TableFieldData(15, 0, "Num Repeats", None, False)
        fields["TRIGGER"] = TableFieldData(19, 16, "Choices", ["A", "b", "CC"],
                                           False)
        fields["POSITION"] = TableFieldData(63, 32, "Position", None, True)
        fields["TIME1"] = TableFieldData(95, 64, "Time Phase A", None, False)
        fields["OUTA1"] = TableFieldData(20, 20, "Out1", None, False)
        fields["TIME2"] = TableFieldData(127, 96, "Time Phase B", None, False)
        fields["OUTA2"] = TableFieldData(26, 26, "Out2", None, False)
        self.client.get_table_fields.return_value = fields
        self.meta = TableMeta("Seq table", writeable=True)
        self.o = PandATablePart(self.client,
                                self.meta,
                                block_name="SEQ1",
                                field_name="TABLE")

    def assert_meta(self, meta, cls, **attrs):
        self.assertIsInstance(meta, cls)
        for k, v in attrs.items():
            assert meta[k] == v

    def test_init(self):
        assert list(self.meta.elements) == [
            "nrepeats",
            "trigger",
            "position",
            "time1",
            "outa1",
            "time2",
            "outa2",
        ]
        self.assert_meta(
            self.meta.elements["nrepeats"],
            NumberArrayMeta,
            dtype="uint16",
            tags=["widget:textinput"],
            description="Num Repeats",
        )
        self.assert_meta(
            self.meta.elements["trigger"],
            ChoiceArrayMeta,
            tags=["widget:combo"],
            description="Choices",
            choices=["A", "b", "CC"],
        )
        self.assert_meta(
            self.meta.elements["position"],
            NumberArrayMeta,
            dtype="int32",
            tags=["widget:textinput"],
            description="Position",
        )
        self.assert_meta(
            self.meta.elements["time1"],
            NumberArrayMeta,
            dtype="uint32",
            tags=["widget:textinput"],
            description="Time Phase A",
        )
        self.assert_meta(
            self.meta.elements["outa1"],
            BooleanArrayMeta,
            tags=["widget:checkbox"],
            description="Out1",
        )
        self.assert_meta(
            self.meta.elements["time2"],
            NumberArrayMeta,
            dtype="uint32",
            tags=["widget:textinput"],
            description="Time Phase B",
        )
        self.assert_meta(
            self.meta.elements["outa2"],
            BooleanArrayMeta,
            tags=["widget:checkbox"],
            description="Out2",
        )

    def test_list_from_table(self):
        table = self.meta.validate(
            self.meta.table_cls.from_rows([
                [32, "b", -1, 4096, True, 4097, False],
                [0, "b", 1, 0, False, 200, True],
                [0, "CC", 0, 6, True, 200, False],
            ]))
        li = self.o.list_from_table(table)
        assert all(li == ([
            0x00110020,
            4294967295,
            4096,
            4097,
            0x04010000,
            1,
            0,
            200,
            0x00120000,
            0,
            6,
            200,
        ]))

    def test_table_from_list(self):
        li = [
            0x00110020,
            4294967295,
            4096,
            4097,
            0x04010000,
            1,
            0,
            200,
            0x00120000,
            0,
            6,
            200,
        ]
        table = self.o.table_from_list([str(x) for x in li])
        assert table.nrepeats == [32, 0, 0]
        assert table.trigger == ["b", "b", "CC"]
        assert table.position == [-1, 1, 0]
        assert table.time1 == [4096, 0, 6]
        assert table.outa1 == [True, False, True]
        assert table.time2 == [4097, 200, 200]
        assert table.outa2 == [False, True, False]