def setUp(self, mock_client, catools):
     self.process = Mock()
     self.o = PandABlocksRunnableController(mri="P",
                                            config_dir="/tmp",
                                            prefix="PV:")
     self.o.setup(self.process)
     blocks_data = OrderedDict()
     fields = OrderedDict()
     fields["TS"] = FieldData("ext_out", "", "Timestamp", ["No", "Capture"])
     blocks_data["PCAP"] = BlockData(1, "", fields)
     fields = OrderedDict()
     fields["VAL"] = FieldData("pos_out", "", "Output", ["No", "Capture"])
     blocks_data["INENC"] = BlockData(1, "", fields)
     self.client = self.o.client
     self.client.get_blocks_data.return_value = blocks_data
     self.o._make_blocks_parts()
Ejemplo n.º 2
0
    def test_block_fields_lut(self):
        fields = OrderedDict()
        block_data = BlockData(8, "Lut description", fields)
        fields["FUNC"] = FieldData("param", "lut", "Function", [])

        o = PandABlockController(self.client, "MRI", "LUT3", block_data, "/docs")
        self.process.add_controller(o)
        b = self.process.block_view("MRI:LUT3")

        func = b.func
        assert func.meta.writeable is True
        assert func.meta.typeid == StringMeta.typeid
        assert func.meta.tags == ["group:parameters", "widget:textinput", "config:1"]

        queue = Queue()
        subscribe = Subscribe(path=["MRI:LUT3"], delta=True)
        subscribe.set_callback(queue.put)
        o.handle_request(subscribe)
        delta = queue.get()
        assert delta.changes[0][1]["func"]["value"] == ""
        assert '<path id="OR"' in delta.changes[0][1]["icon"]["value"]

        # This is the correct FUNC.RAW value for !A&!B&!C&!D&!E
        self.client.get_field.return_value = "1"
        ts = TimeStamp()
        o.handle_changes({"FUNC": "!A&!B&!C&!D&!E"}, ts)
        self.client.get_field.assert_called_once_with("LUT3", "FUNC.RAW")
        delta = queue.get()
        assert delta.changes == [
            [["func", "value"], "!A&!B&!C&!D&!E"],
            [["func", "timeStamp"], ts],
            [["icon", "value"], ANY],
            [["icon", "timeStamp"], ts],
        ]
        assert '<path id="OR"' not in delta.changes[2][1]
Ejemplo n.º 3
0
 def test_block_data(self):
     messages = [
         "!TTLIN 6\n!TTLOUT 10\n.\n",
         "OK =TTL input\n",
         "OK =TTL output\n",
         "!VAL 1 ext_out funny\n!TERM 0 param enum\n.\n",
         "!VAL 0 bit_mux\n.\n",
         "OK =TTL termination\n",
         "OK =TTL input value\n",
         "!High-Z\n!50-Ohm\n.\n",
         "!No\n!Value\n.\n",
         "OK =TTL output value\n",
         "!ZERO\n!TTLIN1.VAL\n!TTLIN2.VAL\n.\n",
     ]
     self.start(messages)
     block_data = self.c.get_blocks_data()
     self.c.stop()
     assert self.socket.sendall.call_args_list == [
         call(b"*BLOCKS?\n"),
         call(b"*DESC.TTLIN?\n"),
         call(b"*DESC.TTLOUT?\n"),
         call(b"TTLIN.*?\n"),
         call(b"TTLOUT.*?\n"),
         call(b"*DESC.TTLIN.TERM?\n"),
         call(b"*DESC.TTLIN.VAL?\n"),
         call(b"*ENUMS.TTLIN.TERM?\n"),
         call(b"*ENUMS.TTLIN.VAL.CAPTURE?\n"),
         call(b"*DESC.TTLOUT.VAL?\n"),
         call(b"*ENUMS.TTLOUT.VAL?\n"),
     ]
     assert list(block_data) == ["TTLIN", "TTLOUT"]
     in_fields = OrderedDict()
     in_fields["TERM"] = FieldData(
         "param", "enum", "TTL termination", ["High-Z", "50-Ohm"]
     )
     in_fields["VAL"] = FieldData(
         "ext_out", "funny", "TTL input value", ["No", "Value"]
     )
     assert block_data["TTLIN"] == (BlockData(6, "TTL input", in_fields))
     out_fields = OrderedDict()
     out_fields["VAL"] = FieldData(
         "bit_mux", "", "TTL output value", ["ZERO", "TTLIN1.VAL", "TTLIN2.VAL"]
     )
     assert block_data["TTLOUT"] == (BlockData(10, "TTL output", out_fields))
Ejemplo n.º 4
0
 def setUp(self, mock_client, mock_adbase_parts):
     mock_adbase_parts.return_value = ([], [])
     self.process = Process()
     self.o = PandARunnableController(
         mri="P", config_dir="/tmp", prefix="PV:", use_git=False
     )
     self.o.add_part(DatasetTablePart("DSET"))
     self.client = self.o._client
     self.client.started = False
     blocks_data = OrderedDict()
     fields = OrderedDict()
     fields["TS"] = FieldData("ext_out", "", "Timestamp", ["No", "Capture"])
     blocks_data["PCAP"] = BlockData(1, "", fields)
     fields = OrderedDict()
     fields["VAL"] = FieldData("pos_out", "", "Output", ["No", "Capture"])
     blocks_data["INENC"] = BlockData(4, "", fields)
     self.client.get_blocks_data.return_value = blocks_data
     self.process.add_controller(self.o)
     self.process.start()
Ejemplo n.º 5
0
 def setUp(self, mock_client):
     self.process = Process()
     self.config_dir = tmp_dir("config_dir")
     self.o = PandAManagerController(mri="P",
                                     config_dir=self.config_dir.value,
                                     poll_period=1000)
     self.client = self.o._client
     self.client.started = False
     blocks_data = OrderedDict()
     fields = OrderedDict()
     fields["INP"] = FieldData("pos_mux", "", "Input A",
                               ["ZERO", "COUNTER.OUT"])
     fields["START"] = FieldData("param", "", "Start position", [])
     fields["STEP"] = FieldData("param", "", "Step position", [])
     fields["OUT"] = FieldData("bit_out", "", "Output", [])
     blocks_data["PCOMP"] = BlockData(1, "Position Compare", fields)
     fields = OrderedDict()
     fields["INP"] = FieldData("bit_mux", "", "Input",
                               ["ZERO", "TTLIN.VAL"])
     fields["START"] = FieldData("param", "pos", "Start position", [])
     fields["OUT"] = FieldData("pos_out", "", "Output", ["No", "Capture"])
     blocks_data["COUNTER"] = BlockData(1, "", fields)
     fields = OrderedDict()
     fields["VAL"] = FieldData("bit_out", "", "Output", [])
     blocks_data["TTLIN"] = BlockData(2, "", fields)
     blocks_data["PCAP"] = BlockData(1, "", {})
     self.client.get_blocks_data.return_value = blocks_data
     changes = [
         ["PCOMP.INP", "ZERO"],
         ["PCOMP.STEP", "0"],
         ["PCOMP.START", "0"],
         ["PCOMP.OUT", "0"],
         ["COUNTER.INP", "ZERO"],
         ["COUNTER.INP.DELAY", "0"],
         ["COUNTER.OUT", "0"],
         ["COUNTER.OUT.SCALE", "1"],
         ["COUNTER.OUT.OFFSET", "0"],
         ["COUNTER.OUT.UNITS", ""],
         ["TTLIN1.VAL", "0"],
         ["TTLIN2.VAL", "0"],
     ]
     self.client.get_changes.return_value = changes
     pcap_bit_fields = {
         "PCAP.BITS0.CAPTURE":
         ["TTLIN1.VAL", "TTLIN2.VAL", "PCOMP.OUT", ""]
     }
     self.client.get_pcap_bits_fields.return_value = pcap_bit_fields
     self.process.add_controller(self.o)
     self.process.start()
 def setUp(self, mock_client):
     self.o = PandABlocksManagerController(mri="P", config_dir="/tmp")
     self.process = MagicMock()
     self.o.setup(self.process)
     blocks_data = OrderedDict()
     fields = OrderedDict()
     fields["INP"] = FieldData("pos_mux", "", "Input A",
                               ["ZERO", "COUNTER.OUT"])
     fields["START"] = FieldData("param", "", "Start position", [])
     fields["STEP"] = FieldData("param", "", "Step position", [])
     fields["OUT"] = FieldData("bit_out", "", "Output", [])
     blocks_data["PCOMP"] = BlockData(1, "", fields)
     fields = OrderedDict()
     fields["INP"] = FieldData("bit_mux", "", "Input",
                               ["ZERO", "TTLIN.VAL"])
     fields["START"] = FieldData("param", "pos", "Start position", [])
     fields["OUT"] = FieldData("pos_out", "", "Output", ["No", "Capture"])
     blocks_data["COUNTER"] = BlockData(1, "", fields)
     fields = OrderedDict()
     fields["VAL"] = FieldData("bit_out", "", "Output", [])
     blocks_data["TTLIN"] = BlockData(1, "", fields)
     self.client = self.o.client
     self.client.get_blocks_data.return_value = blocks_data
     self.o._make_blocks_parts()
     changes = OrderedDict()
     changes["PCOMP.INP"] = "ZERO"
     for field_name in ("START", "STEP"):
         changes["PCOMP.%s" % field_name] = "0"
     changes["PCOMP.OUT"] = "0"
     changes["COUNTER.INP"] = "ZERO"
     changes["COUNTER.INP.DELAY"] = "0"
     changes["COUNTER.OUT"] = "0"
     changes["COUNTER.OUT.SCALE"] = "1"
     changes["COUNTER.OUT.OFFSET"] = "0"
     changes["COUNTER.OUT.UNITS"] = ""
     changes["TTLIN.VAL"] = "0"
     self.o.handle_changes(changes.items())
     # Once more to let the bit_outs toggle back
     self.o.handle_changes(())
Ejemplo n.º 7
0
    def test_block_fields_adder(self):
        fields = OrderedDict()
        block_data = BlockData(2, "Adder description", fields)
        fields["INPA"] = FieldData("pos_mux", "", "Input A",
                                   ["A.OUT", "B.OUT"])
        fields["INPB"] = FieldData("pos_mux", "", "Input B",
                                   ["A.OUT", "B.OUT"])
        fields["DIVIDE"] = FieldData("param", "enum", "Divide output",
                                     ["/1", "/2", "/4"])
        fields["OUT"] = FieldData("pos_out", "", "Output", ["No", "Capture"])
        o = PandABlocksMaker(self.client, "ADDER1", block_data, "/docs")
        assert list(o.parts) == [
            'icon', 'label', 'help', 'inputs', 'INPA', 'INPA.CURRENT', 'INPB',
            'INPB.CURRENT', 'parameters', 'DIVIDE', 'outputs', 'OUT',
            'OUT.UNITS', 'OUT.SCALE', 'OUT.OFFSET', 'OUT.SCALED', 'OUT.CAPTURE'
        ]

        group = o.parts["inputs"]
        assert group.name == "inputs"
        assert group.attr.meta.tags == ["widget:group", "config:1"]

        inpa = o.parts["INPA"]
        assert inpa.block_name == "ADDER1"
        assert inpa.field_name == "INPA"
        assert inpa.meta.writeable == True
        self.assertIsInstance(inpa.meta, ChoiceMeta)
        assert inpa.meta.tags == [
            "group:inputs", "sinkPort:int32:ZERO", "widget:combo", "config:1"
        ]
        assert inpa.meta.choices == ["A.OUT", "B.OUT"]

        val = o.parts["INPA.CURRENT"]
        assert val.block_name == "ADDER1"
        assert val.meta.writeable == False
        self.assertIsInstance(val.meta, NumberMeta)
        assert val.meta.dtype == "int32"
        assert val.meta.tags == ["group:inputs", "widget:textupdate"]

        valb = o.parts["INPB"]
        assert valb.field_name == "INPB"

        divide = o.parts["DIVIDE"]
        assert divide.block_name == "ADDER1"
        assert divide.field_name == "DIVIDE"
        assert divide.meta.writeable == True
        self.assertIsInstance(divide.meta, ChoiceMeta)
        assert divide.meta.tags == [
            "group:parameters", "widget:combo", "config:1"
        ]
        assert divide.meta.choices == ["/1", "/2", "/4"]

        out = o.parts["OUT"]
        assert out.block_name == "ADDER1"
        assert out.field_name == "OUT"
        assert out.meta.writeable == False
        self.assertIsInstance(out.meta, NumberMeta)
        assert out.meta.dtype == "int32"
        assert out.meta.tags == [
            "group:outputs", "sourcePort:int32:ADDER1.OUT", "widget:textupdate"
        ]

        units = o.parts["OUT.UNITS"]
        assert units.block_name == "ADDER1"
        assert units.field_name == "OUT.UNITS"
        assert units.meta.writeable == True
        self.assertIsInstance(units.meta, StringMeta)
        assert units.meta.tags == [
            "group:outputs", "widget:textinput", "config:1"
        ]

        scale = o.parts["OUT.SCALE"]
        assert scale.block_name == "ADDER1"
        assert scale.field_name == "OUT.SCALE"
        assert scale.meta.writeable == True
        self.assertIsInstance(scale.meta, NumberMeta)
        assert scale.meta.dtype == "float64"
        assert scale.meta.tags == [
            "group:outputs", "widget:textinput", "config:1"
        ]

        offset = o.parts["OUT.OFFSET"]
        assert offset.block_name == "ADDER1"
        assert offset.field_name == "OUT.OFFSET"
        assert offset.meta.writeable == True
        self.assertIsInstance(offset.meta, NumberMeta)
        assert offset.meta.dtype == "float64"
        assert offset.meta.tags == [
            "group:outputs", "widget:textinput", "config:1"
        ]

        capture = o.parts["OUT.CAPTURE"]
        assert capture.block_name == "ADDER1"
        assert capture.field_name == "OUT.CAPTURE"
        assert capture.meta.writeable == True
        self.assertIsInstance(capture.meta, ChoiceMeta)
        assert capture.meta.tags == [
            "group:outputs", "widget:combo", "config:1"
        ]
        assert capture.meta.choices == ["No", "Capture"]

        scale = o.parts["OUT.SCALED"]
        assert scale.block_name == "ADDER1"
        assert scale.meta.writeable == False
        self.assertIsInstance(scale.meta, NumberMeta)
        assert scale.meta.dtype == "float64"
        assert scale.meta.tags == ["group:outputs", "widget:textupdate"]
Ejemplo n.º 8
0
    def test_block_fields_pulse(self):
        fields = OrderedDict()
        block_data = BlockData(4, "Pulse description", fields)
        fields["DELAY"] = FieldData("time", "", "Time", [])
        fields["INP"] = FieldData("bit_mux", "", "Input", ["X.OUT", "Y.OUT"])
        fields["OUT"] = FieldData("bit_out", "", "Output", [])
        fields["ERR_PERIOD"] = FieldData("read", "bit", "Error", [])
        o = PandABlocksMaker(self.client, "PULSE2", block_data, "/docs")
        assert list(o.parts) == [
            'icon', 'label', 'help', 'parameters', 'DELAY', 'DELAY.UNITS',
            'inputs', 'INP', 'INP.CURRENT', 'INP.DELAY', 'outputs', 'OUT',
            'readbacks', 'ERR_PERIOD'
        ]

        help = o.parts['help']
        assert help.attr.value == "/docs/build/pulse_doc.html"

        delay = o.parts["DELAY"]
        assert delay.block_name == "PULSE2"
        assert delay.field_name == "DELAY"
        assert delay.meta.writeable == True
        self.assertIsInstance(delay.meta, NumberMeta)
        assert delay.meta.dtype == "float64"
        assert delay.meta.tags == [
            "group:parameters", "widget:textinput", "config:2"
        ]

        units = o.parts["DELAY.UNITS"]
        assert units.block_name == "PULSE2"
        assert units.field_name == "DELAY.UNITS"
        assert units.meta.writeable == True
        self.assertIsInstance(units.meta, ChoiceMeta)
        assert units.meta.tags == [
            "group:parameters", "widget:combo", "config:1"
        ]
        assert units.meta.choices == ["s", "ms", "us"]

        inp = o.parts["INP"]
        assert inp.block_name == "PULSE2"
        assert inp.field_name == "INP"
        assert inp.meta.writeable == True
        self.assertIsInstance(inp.meta, ChoiceMeta)
        assert inp.meta.tags == [
            "group:inputs", "sinkPort:bool:ZERO", "widget:combo", "config:1"
        ]
        assert inp.meta.choices == ["X.OUT", "Y.OUT"]

        val = o.parts["INP.CURRENT"]
        assert val.block_name == "PULSE2"
        assert val.meta.writeable == False
        self.assertIsInstance(val.meta, BooleanMeta)
        assert val.meta.tags == ["group:inputs", "widget:led"]

        delay = o.parts["INP.DELAY"]
        assert delay.block_name == "PULSE2"
        assert delay.field_name == "INP.DELAY"
        assert delay.meta.writeable == True
        self.assertIsInstance(delay.meta, NumberMeta)
        assert delay.meta.dtype == "uint8"
        assert delay.meta.tags == [
            "group:inputs", "widget:textinput", "config:1"
        ]

        out = o.parts["OUT"]
        assert out.block_name == "PULSE2"
        assert out.field_name == "OUT"
        assert out.meta.writeable == False
        self.assertIsInstance(out.meta, BooleanMeta)
        assert out.meta.tags == [
            "group:outputs", "sourcePort:bool:PULSE2.OUT", "widget:led"
        ]

        err = o.parts["ERR_PERIOD"]
        assert err.block_name == "PULSE2"
        assert err.field_name == "ERR_PERIOD"
        assert err.meta.writeable == False
        self.assertIsInstance(err.meta, BooleanMeta)
        assert err.meta.tags == ["group:readbacks", "widget:led"]
Ejemplo n.º 9
0
    def test_block_fields_adder(self):
        fields = OrderedDict()
        block_data = BlockData(2, "Adder description", fields)
        fields["INPA"] = FieldData("pos_mux", "", "Input A", ["A.OUT", "B.OUT"])
        fields["INPB"] = FieldData("pos_mux", "", "Input B", ["A.OUT", "B.OUT"])
        fields["DIVIDE"] = FieldData(
            "param", "enum", "Divide output", ["/1", "/2", "/4"]
        )
        fields["OUT"] = FieldData("pos_out", "", "Output", ["No", "Capture"])
        fields["HEALTH"] = FieldData("read", "enum", "What's wrong", ["OK", "Very Bad"])

        o = PandABlockController(self.client, "MRI", "ADDER1", block_data, "/docs")
        self.process.add_controller(o)
        b = self.process.block_view("MRI:ADDER1")

        assert list(b) == [
            "meta",
            "health",
            "icon",
            "label",
            "help",
            "inputs",
            "inpa",
            "inpb",
            "parameters",
            "divide",
            "outputs",
            "out",
        ]

        group = b.inputs
        assert group.meta.tags == ["widget:group", "config:1"]

        inpa = b.inpa
        assert inpa.meta.writeable is True
        assert inpa.meta.typeid == ChoiceMeta.typeid
        assert inpa.meta.tags == [
            "group:inputs",
            "sinkPort:int32:ZERO",
            "widget:combo",
            "config:1",
        ]
        assert inpa.meta.choices == ["A.OUT", "B.OUT"]
        inpa.put_value("A.OUT")
        self.client.set_field.assert_called_once_with("ADDER1", "INPA", "A.OUT")
        self.client.reset_mock()

        divide = b.divide
        assert divide.meta.writeable is True
        assert divide.meta.typeid == ChoiceMeta.typeid
        assert divide.meta.tags == ["group:parameters", "widget:combo", "config:1"]
        assert divide.meta.choices == ["/1", "/2", "/4"]

        out = b.out
        assert out.meta.writeable is False
        assert out.meta.typeid == NumberMeta.typeid
        assert out.meta.dtype == "int32"
        assert out.meta.tags == [
            "group:outputs",
            "sourcePort:int32:ADDER1.OUT",
            "widget:textupdate",
        ]

        queue = Queue()
        subscribe = Subscribe(path=["MRI:ADDER1", "out"], delta=True)
        subscribe.set_callback(queue.put)
        o.handle_request(subscribe)
        delta = queue.get(timeout=1)
        assert delta.changes[0][1]["value"] == 0

        ts = TimeStamp()
        o.handle_changes({"OUT": "145"}, ts)
        delta = queue.get(timeout=1)
        assert delta.changes == [
            [["value"], 145],
            [["timeStamp"], ts],
        ]

        subscribe = Subscribe(path=["MRI:ADDER1", "health"], delta=True)
        subscribe.set_callback(queue.put)
        o.handle_request(subscribe)
        delta = queue.get(timeout=1)
        assert delta.changes[0][1]["value"] == "OK"

        ts = TimeStamp()
        o.handle_changes({"HEALTH": "Very Bad"}, ts)
        delta = queue.get(timeout=1)
        assert delta.changes == [
            [["value"], "Very Bad"],
            [["alarm"], Alarm.major("Very Bad")],
            [["timeStamp"], ts],
        ]
        o.handle_changes({"HEALTH": "OK"}, ts)
        delta = queue.get(timeout=1)
        assert delta.changes == [
            [["value"], "OK"],
            [["alarm"], Alarm.ok],
            [["timeStamp"], ts],
        ]
Ejemplo n.º 10
0
    def test_block_fields_pulse(self):
        fields = OrderedDict()
        block_data = BlockData(4, "Pulse description", fields)
        fields["DELAY"] = FieldData("time", "", "Time", [])
        fields["INP"] = FieldData("bit_mux", "", "Input", ["ZERO", "X.OUT", "Y.OUT"])
        fields["OUT"] = FieldData("bit_out", "", "Output", [])
        fields["ERR_PERIOD"] = FieldData("read", "bit", "Error", [])

        o = PandABlockController(self.client, "MRI", "PULSE2", block_data, "/docs")
        self.process.add_controller(o)
        b = self.process.block_view("MRI:PULSE2")

        assert list(b) == [
            "meta",
            "health",
            "icon",
            "label",
            "help",
            "parameters",
            "delay",
            "delayUnits",
            "inputs",
            "inp",
            "inpDelay",
            "outputs",
            "out",
            "readbacks",
            "errPeriod",
        ]

        assert b.meta.label == "Pulse description 2"
        assert b.label.value == "Pulse description 2"

        # check setting label
        b.label.put_value("A new label")
        assert b.meta.label == "A new label"
        assert b.label.value == "A new label"
        self.client.set_field.assert_called_once_with(
            "*METADATA", "LABEL_PULSE2", "A new label"
        )
        self.client.set_field.reset_mock()

        # check updated with nothing
        o.handle_changes(dict(LABEL=""), ts=TimeStamp())
        assert b.meta.label == "Pulse description 2"
        assert b.label.value == "Pulse description 2"
        self.client.set_field.assert_not_called()

        # check updated with something from the server
        o.handle_changes(dict(LABEL="A server label"), ts=TimeStamp())
        assert b.meta.label == "A server label"
        assert b.label.value == "A server label"
        self.client.set_field.assert_not_called()

        help = b.help
        assert help.value == "/docs/build/pulse_doc.html"

        delay = b.delay
        assert delay.meta.writeable is True
        assert delay.meta.typeid == NumberMeta.typeid
        assert delay.meta.dtype == "float64"
        assert delay.meta.tags == ["group:parameters", "widget:textinput", "config:2"]

        units = b.delayUnits
        assert units.meta.writeable is True
        assert units.meta.typeid == ChoiceMeta.typeid
        assert units.meta.tags == ["group:parameters", "widget:combo", "config:1"]
        assert units.meta.choices == ["s", "ms", "us"]

        inp = b.inp
        assert inp.meta.writeable is True
        assert inp.meta.typeid == ChoiceMeta.typeid
        assert inp.meta.tags == [
            "group:inputs",
            "sinkPort:bool:ZERO",
            "widget:combo",
            "badgevalue:plus:inpDelay:MRI:PULSE2",
            "config:1",
        ]
        assert inp.meta.choices == ["ZERO", "X.OUT", "Y.OUT"]

        delay = b.inpDelay
        assert delay.meta.writeable is True
        assert delay.meta.typeid == NumberMeta.typeid
        assert delay.meta.dtype == "uint8"
        assert delay.meta.tags == ["group:inputs", "widget:textinput", "config:1"]

        out = b.out
        assert out.meta.writeable is False
        assert out.meta.typeid == BooleanMeta.typeid
        assert out.meta.tags == [
            "group:outputs",
            "sourcePort:bool:PULSE2.OUT",
            "widget:led",
        ]

        err = b.errPeriod
        assert err.meta.writeable is False
        assert err.meta.typeid == BooleanMeta.typeid
        assert err.meta.tags == ["group:readbacks", "widget:led"]

        queue = Queue()
        subscribe = Subscribe(path=["MRI:PULSE2", "inp"], delta=True)
        subscribe.set_callback(queue.put)
        o.handle_request(subscribe)
        delta = queue.get()
        assert delta.changes[0][1]["value"] == "ZERO"

        ts = TimeStamp()
        o.handle_changes({"INP": "X.OUT"}, ts)
        delta = queue.get()
        assert delta.changes == [
            [["value"], "X.OUT"],
            [["timeStamp"], ts],
            [
                ["meta", "tags"],
                [
                    "group:inputs",
                    "sinkPort:bool:ZERO",
                    "widget:combo",
                    "badgevalue:plus:inpDelay:MRI:PULSE2",
                    "config:1",
                    "linkedvalue:out:MRI:X",
                ],
            ],
        ]