示例#1
0
    def sync_proxy(self, mri, block):
        """Abstract method telling the ClientComms to sync this proxy Block
        with its remote counterpart. Should wait until it is connected

        Args:
            mri (str): The mri for the remote block
            block (BlockModel): The local proxy Block to keep in sync
        """
        # Send a root Subscribe to the server
        subscribe = Subscribe(path=[mri], delta=True)
        done_queue = Queue()

        def handle_response(response):
            # Called from tornado
            if not isinstance(response, Delta):
                # Return or Error is the end of our subscription, log and ignore
                self.log.debug("Proxy got response %r", response)
                done_queue.put(None)
            else:
                cothread.Callback(self._handle_response, response, block,
                                  done_queue)

        subscribe.set_callback(handle_response)
        IOLoopHelper.call(self._send_request, subscribe)
        done_queue.get(timeout=DEFAULT_TIMEOUT)
    def test_pos_table_deltas(self):
        queue = Queue()
        subscribe = Subscribe(path=["P"], delta=True)
        subscribe.set_callback(queue.put)
        self.o.handle_request(subscribe)
        delta = queue.get()
        capture_enums = delta.changes[0][1]["positions"]["meta"]["elements"][
            "capture"]["choices"]
        assert capture_enums[0] == PositionCapture.NO
        table = delta.changes[0][1]["positions"]["value"]
        assert table.name == ["COUNTER.OUT"]
        assert table.value == [0.0]
        assert table.scale == [1.0]
        assert table.offset == [0.0]
        assert table.capture == [PositionCapture.NO]

        self.o.handle_changes([("COUNTER.OUT", "20")])
        delta = queue.get()
        assert delta.changes == [
            [["positions", "value", "value"], [20.0]],
            [["positions", "timeStamp"], ANY],
        ]

        self.o.handle_changes([("COUNTER.OUT", "5"),
                               ("COUNTER.OUT.SCALE", 0.5)])
        delta = queue.get()
        assert delta.changes == [
            [["positions", "value", "value"], [2.5]],
            [["positions", "value", "scale"], [0.5]],
            [["positions", "timeStamp"], ANY],
        ]
示例#3
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]
示例#4
0
 def _start_client(self):
     # Called from cothread
     if self._conn is None:
         IOLoopHelper.call(self.recv_loop)
         self._connected_queue.get(timeout=self.connect_timeout)
         root_subscribe = Subscribe(path=[".", "blocks", "value"])
         root_subscribe.set_callback(self._update_remote_blocks)
         IOLoopHelper.call(self._send_request, root_subscribe)
示例#5
0
 def init(self, context):
     # type: (AContext) -> None
     super(RunnableChildPart, self).init(context)
     # Monitor the child configure Method for changes
     subscription = Subscribe(path=[self.mri, "configure"], delta=True)
     subscription.set_callback(self.update_part_configure_args)
     # Wait for the first update to come in
     self.child_controller.handle_request(subscription).wait()
示例#6
0
 def onFirstConnect(self, pv: SharedPV) -> None:
     # Store the PV, but don't open it now, let the first Delta do this
     with self._lock:
         self.pv = pv
     path = [self.controller.mri]
     if self.field is not None:
         path.append(self.field)
     request = Subscribe(path=path, delta=True)
     request.set_callback(self.handle)
     # No need to wait for first update here
     self.controller.handle_request(request)
示例#7
0
 def on_init(self, context: AContext) -> None:
     self.child_controller = context.get_controller(self.mri)
     if self.stateful:
         # Wait for a while until the child is ready as it changes the
         # save state
         wait_for_stateful_block_init(context, self.mri)
     # Save what we have
     self.on_save(context)
     subscribe = Subscribe(path=[self.mri, "meta", "fields"])
     subscribe.set_callback(self.update_part_exportable)
     # Wait for the first update to come in
     assert self.child_controller, "No child controller"
     self.child_controller.handle_request(subscribe).wait()
示例#8
0
    def _make_export_field(self, mri, attr_name, export_name):
        controller = self.process.get_controller(mri)
        path = [mri, attr_name]
        label = camel_to_title(export_name)
        ret = {}

        def update_field(response):
            if not isinstance(response, Delta):
                # Return or Error is the end of our subscription, log and ignore
                self.log.debug("Export got response %r", response)
                return
            if not ret:
                # First call, create the initial object
                export = deserialize_object(response.changes[0][1])
                if isinstance(export, AttributeModel):

                    def setter(v):
                        context = Context(self.process)
                        context.put(path, v)

                    # Strip out tags that we shouldn't export
                    # TODO: need to strip out port tags too...
                    export.meta.set_tags(
                        without_config_tags(
                            without_group_tags(export.meta.tags)))

                    ret["setter"] = setter
                else:

                    def setter_star_args(*args):
                        context = Context(self.process)
                        context.post(path, *args)

                    ret["setter"] = setter_star_args

                # Regenerate label
                export.meta.set_label(label)
                ret["export"] = export
            else:
                # Subsequent calls, update it
                with self.changes_squashed:
                    for change in response.changes:
                        ret["export"].apply_change(*change)

        subscription = Subscribe(path=path, delta=True)
        subscription.set_callback(update_field)
        self._subscriptions.append(subscription)
        # When we have waited for the subscription, the first update_field
        # will have been called
        controller.handle_request(subscription).wait()
        return ret["export"], ret["setter"]
    def test_table_deltas(self):
        queue = Queue()
        subscribe = Subscribe(path=["P"], delta=True)
        subscribe.set_callback(queue.put)
        self.o.handle_request(subscribe)
        delta = queue.get()
        table = delta.changes[0][1]["bits"]["value"]
        assert table.name == ["TTLIN1.VAL", "TTLIN2.VAL", "PCOMP.OUT"]
        assert table.value == [False, False, False]
        assert table.capture == [False, False, False]

        self.o.handle_changes([("TTLIN1.VAL", "1")])
        delta = queue.get()
        assert delta.changes == [
            [["bits", "value", "value"], [True, False, False]],
            [["bits", "timeStamp"], ANY],
        ]
示例#10
0
 def test_counter_subscribe(self):
     q = Queue()
     # Subscribe to the value
     sub = Subscribe(id=20, path=["counting", "counter"], delta=False)
     sub.set_callback(q.put)
     self.controller.handle_request(sub)
     # Check initial return
     response = q.get(timeout=1.0)
     self.assertIsInstance(response, Update)
     assert response.id == 20
     assert response.value["typeid"] == "epics:nt/NTScalar:1.0"
     assert response.value["value"] == 0
     # Post increment()
     post = Post(id=21, path=["counting", "increment"])
     post.set_callback(q.put)
     self.controller.handle_request(post)
     # Check the value updates...
     response = q.get(timeout=1)
     self.assertIsInstance(response, Update)
     assert response.id == 20
     assert response.value["value"] == 1
     # ... then we get the return
     response = q.get(timeout=1)
     self.assertIsInstance(response, Return)
     assert response.id == 21
     assert response.value is None
     # Check we can put too
     put = Put(id=22, path=["counting", "counter", "value"], value=31)
     put.set_callback(q.put)
     self.controller.handle_request(put)
     # Check the value updates...
     response = q.get(timeout=1)
     self.assertIsInstance(response, Update)
     assert response.id == 20
     assert response.value["value"] == 31
     # ... then we get the return
     response = q.get(timeout=1)
     self.assertIsInstance(response, Return)
     assert response.id == 22
     assert response.value is None
     # And that there isn't anything else
     with self.assertRaises(TimeoutError):
         q.get(timeout=0.05)
示例#11
0
 def test_concurrency(self):
     q = Queue()
     # Subscribe to the whole block
     sub = Subscribe(id=0, path=["mri"], delta=True)
     sub.set_callback(q.put)
     self.c.handle_request(sub)
     # We should get first Delta through with initial value
     r = q.get().to_dict()
     assert r["id"] == 0
     assert len(r["changes"]) == 1
     assert len(r["changes"][0]) == 2
     assert r["changes"][0][0] == []
     assert r["changes"][0][1]["meta"]["label"] == "My label"
     assert r["changes"][0][1]["label"]["value"] == "My label"
     # Do a Put on the label
     put = Put(id=2, path=["mri", "label", "value"], value="New", get=True)
     put.set_callback(q.put)
     self.c.handle_request(put)
     # Check we got two updates before the return
     r = q.get().to_dict()
     assert r["id"] == 0
     assert len(r["changes"]) == 2
     assert len(r["changes"][0]) == 2
     assert r["changes"][0][0] == ["label", "value"]
     assert r["changes"][0][1] == "New"
     assert len(r["changes"][0]) == 2
     assert r["changes"][1][0] == ["label", "timeStamp"]
     r = q.get().to_dict()
     assert r["id"] == 0
     assert len(r["changes"]) == 1
     assert len(r["changes"][0]) == 2
     assert r["changes"][0][0] == ["meta", "label"]
     assert r["changes"][0][1] == "New"
     # Then the return
     r3 = q.get().to_dict()
     assert r3["id"] == 2
     assert r3["value"] == "New"
示例#12
0
 def test_concurrent(self):
     q = Queue()
     request = Subscribe(id=40, path=["hello_block", "greet"], delta=True)
     request.set_callback(q.put)
     self.controller.handle_request(request)
     # Get the initial subscribe value
     inital = q.get(timeout=0.1)
     self.assertIsInstance(inital, Delta)
     assert inital.changes[0][1]["took"]["value"] == dict(sleep=0, name="")
     assert inital.changes[0][1]["returned"]["value"] == {"return": ""}
     # Do a greet
     request = Post(id=44,
                    path=["hello_block", "greet"],
                    parameters=dict(name="me", sleep=1))
     request.set_callback(q.put)
     self.controller.handle_request(request)
     # Then an error
     request = Post(id=45, path=["hello_block", "error"])
     request.set_callback(q.put)
     self.controller.handle_request(request)
     # We should quickly get the error response first
     response = q.get(timeout=1.0)
     self.assertIsInstance(response, Error)
     assert response.id == 45
     # Then the long running greet delta
     response = q.get(timeout=3.0)
     self.assertIsInstance(response, Delta)
     assert len(response.changes) == 2
     assert response.changes[0][0] == ["took"]
     took = response.changes[0][1]
     assert took.value == dict(sleep=1, name="me")
     assert took.present == ["name", "sleep"]
     assert took.alarm == Alarm.ok
     assert response.changes[1][0] == ["returned"]
     returned = response.changes[1][1]
     assert returned.value == {"return": "Hello me"}
     assert returned.present == ["return"]
     assert returned.alarm == Alarm.ok
     # Check it took about 1s to run
     assert abs(1 - (returned.timeStamp.to_time() -
                     took.timeStamp.to_time())) < 0.4
     # And it's response
     response = q.get(timeout=1.0)
     self.assertIsInstance(response, Return)
     assert response.id == 44
     assert response.value == "Hello me"
示例#13
0
    def update_part_exportable(self, response: Response) -> None:
        # Get a child context to check if we have a config field
        assert self.child_controller, "No child controller"
        child = self.child_controller.block_view()
        spawned = []
        if isinstance(response, Update):
            new_fields = response.value
            assert isinstance(new_fields, Sequence), f"Bad field list {new_fields}"
        elif isinstance(response, Return):
            # We got a return with None, so clear out all of the
            # config_subscriptions
            new_fields = []
        else:
            self.log.warning("Got unexpected response {response}")
            return

        # Remove any existing subscription that is not in the new fields
        for subscribe in self.config_subscriptions.values():
            attr_name = subscribe.path[-2]
            if attr_name not in new_fields:
                unsubscribe = Unsubscribe(subscribe.id)
                unsubscribe.set_callback(subscribe.callback)
                spawned.append(self.child_controller.handle_request(unsubscribe))
                self.port_infos.pop(attr_name, None)

        # Add a subscription to any new field
        existing_fields = set(s.path[-2] for s in self.config_subscriptions.values())
        for field in set(new_fields) - existing_fields:
            attr = getattr(child, field)
            if isinstance(attr, Attribute):
                # Cache tags here
                tags = attr.meta.tags
                # Check if the attribute has any port tags, and store for
                # when we are asked for LayoutInfo
                port_info = Port.port_tag_details(tags)
                info: PortInfo
                if port_info:
                    is_source, port, extra = port_info
                    if is_source:
                        info = SourcePortInfo(
                            name=field, port=port, connected_value=extra
                        )
                    else:
                        info = SinkPortInfo(
                            name=field,
                            port=port,
                            disconnected_value=extra,
                            value=attr.value,
                        )
                    self.port_infos[field] = info
                # If we are config tagged then subscribe so we can calculate
                # if we are modified
                if self._unmanaged_attr(field) and get_config_tag(tags):
                    if self.config_subscriptions:
                        new_id = max(self.config_subscriptions) + 1
                    else:
                        new_id = 1
                    subscribe = Subscribe(id=new_id, path=[self.mri, field, "value"])
                    subscribe.set_callback(self.update_part_modified)
                    self.config_subscriptions[new_id] = subscribe
                    spawned.append(self.child_controller.handle_request(subscribe))

        # Wait for the first update to come in for every subscription
        for s in spawned:
            s.wait()
        port_infos = [self.port_infos[f] for f in new_fields if f in self.port_infos]
        assert self.registrar, "No registrar assigned"
        self.registrar.report(PartExportableInfo(new_fields, port_infos))
示例#14
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],
        ]
示例#15
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",
                ],
            ],
        ]