예제 #1
0
    def handle_changes(self, changes: Sequence[Tuple[str, str]]) -> None:
        ts = TimeStamp()
        # {block_name: {field_name: field_value}}
        block_changes: Dict[str, Any] = {}
        # {full_field: field_value}
        bus_changes = {}

        # Process bit outs that need changing
        bit_out_changes = self._bit_out_changes
        self._bit_out_changes = {}
        for k, v in bit_out_changes.items():
            self._bit_outs[k] = v
            bus_changes[k] = v
            block_name, field_name = k.split(".")
            block_changes.setdefault(block_name, {})[field_name] = v

        # Work out which change is needed for which block
        for key, value in changes:
            self._handle_change(key, value, bus_changes, block_changes,
                                bit_out_changes)

        # Notify the Blocks that they need to handle these changes
        if bus_changes:
            self.busses.handle_changes(bus_changes, ts)
        for block_name, block_changes_values in block_changes.items():
            self._child_controllers[block_name].handle_changes(
                block_changes_values, ts)
예제 #2
0
 def setUp(self):
     self.serialized = OrderedDict()
     self.serialized["typeid"] = "epics:nt/NTScalar:1.0"
     self.serialized["value"] = "some string"
     self.serialized["alarm"] = Alarm().to_dict()
     self.serialized["timeStamp"] = TimeStamp().to_dict()
     self.serialized["meta"] = StringMeta("desc").to_dict()
예제 #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 test_scale_offset(self):
     ts = TimeStamp()
     changes = {"B1.P0.SCALE": "32", "B1.P0.OFFSET": "0.1", "B1.P0": "100"}
     self.o.handle_changes(changes, ts)
     assert self.o.positions.timeStamp is ts
     assert list(self.o.positions.value.rows())[0] == [
         "B1.P0",
         3200.1,
         "",
         32.0,
         0.1,
         PositionCapture.NO,
     ]
     self.o.handle_changes({"B1.P0.SCALE": "64"}, ts)
     assert list(self.o.positions.value.rows())[0] == [
         "B1.P0",
         6400.1,
         "",
         64.0,
         0.1,
         PositionCapture.NO,
     ]
     self.o.handle_changes({"B1.P0": "200"}, ts)
     assert list(self.o.positions.value.rows())[0] == [
         "B1.P0",
         12800.1,
         "",
         64.0,
         0.1,
         PositionCapture.NO,
     ]
예제 #5
0
 def test_bits(self):
     ts = TimeStamp()
     changes = {
         "B1.B1": True,
         "B1.B3": True,
     }
     self.o.handle_changes(changes, ts)
     assert self.o.bits.timeStamp is ts
     assert list(self.o.bits.value.rows())[1] == ["B1.B1", True, False]
     assert list(self.o.bits.value.rows())[2] == ["B1.B2", False, False]
     assert list(self.o.bits.value.rows())[3] == ["B1.B3", True, False]
    def update_attribute(self, block_name, field_name, value):
        ret = None
        parts = self._blocks_parts[block_name]
        if field_name not in parts:
            self.log.debug("Block %s has no field %s", block_name, field_name)
            return ret
        part = parts[field_name]
        attr = part.attr
        field_data = self._blocks_data[block_name].fields.get(field_name, None)
        if value == Exception:
            # TODO: set error
            self.log.warning("Field %s.%s in error", block_name, field_name)
            value = None

        # Cheaper than isinstance
        if attr.meta.typeid == TableMeta.typeid:
            value = part.table_from_list(value)
        elif attr.meta.typeid == BooleanMeta.typeid:
            value = bool(int(value))
            is_bit_out = field_data and field_data.field_type == "bit_out"
            if is_bit_out and attr.value is value:
                # make bit_out things toggle while changing
                ret = value
                value = not value
        else:
            value = attr.meta.validate(value)

        # Update the value of our attribute and anyone listening
        ts = TimeStamp()
        attr.set_value_alarm_ts(value, Alarm.ok, ts)
        for dest_attr in self._listening_attrs.get(attr, []):
            dest_attr.set_value_alarm_ts(value, Alarm.ok, ts)

        # if we changed the value of a mux, update the slaved values
        if field_data and field_data.field_type in ("bit_mux", "pos_mux"):
            current_part = parts[field_name + ".CURRENT"]
            current_attr = current_part.attr
            self._update_current_attr(
                current_attr, value, ts, field_data.field_type)

        # if we changed a pos_out, its SCALE or OFFSET, update its scaled value
        root_field_name = field_name.split(".")[0]
        field_data = self._blocks_data[block_name].fields[root_field_name]
        if field_data.field_type == "pos_out":
            scale = parts[root_field_name + ".SCALE"].attr.value
            offset = parts[root_field_name + ".OFFSET"].attr.value
            scaled = parts[root_field_name].attr.value * scale + offset
            parts[root_field_name + ".SCALED"].attr.set_value_alarm_ts(
                scaled, Alarm.ok, ts)
        return ret
예제 #7
0
 def _update_value(self, value):
     if not value.ok:
         self.attr.set_value(None, alarm=Alarm.invalid("PV disconnected"))
     else:
         if value.severity:
             alarm = Alarm(severity=value.severity,
                           status=AlarmStatus.RECORD_STATUS,
                           message="PV in alarm state")
         else:
             alarm = Alarm.ok
         # We only have a raw_stamp attr on monitor, the initial
         # caget with CTRL doesn't give us a timestamp
         ts = TimeStamp(*getattr(value, "raw_stamp", (None, None)))
         value = self.attr.meta.validate(value)
         self.attr.set_value_alarm_ts(value, alarm, ts)
예제 #8
0
 def test_pos_capture(self):
     ts = TimeStamp()
     changes = {
         "B1.P2.CAPTURE": "Min Max Mean",
         "B1.P2.SCALE": "1",
         "B1.P2": "100"
     }
     self.o.handle_changes(changes, ts)
     assert list(self.o.positions.value.rows())[2] == [
         "B1.P2",
         100,
         "",
         1.0,
         0.0,
         PositionCapture.MIN_MAX_MEAN,
     ]
예제 #9
0
 def setUp(self):
     elements = OrderedDict()
     elements["foo"] = StringArrayMeta(label="Foo").to_dict()
     elements["bar"] = StringArrayMeta().to_dict()
     meta = OrderedDict()
     meta["typeid"] = "malcolm:core/TableMeta:1.0"
     meta["description"] = "desc"
     meta["tags"] = []
     meta["writeable"] = True
     meta["label"] = "my label"
     meta["elements"] = elements
     value = OrderedDict()
     value["typeid"] = "malcolm:core/Table:1.0"
     value["foo"] = ["foo1", "foo2"]
     value["bar"] = ["bar1", "bar2"]
     self.serialized = OrderedDict()
     self.serialized["typeid"] = "epics:nt/NTTable:1.0"
     self.serialized["labels"] = ["Foo", "bar"]
     self.serialized["value"] = value
     self.serialized["alarm"] = Alarm().to_dict()
     self.serialized["timeStamp"] = TimeStamp().to_dict()
     self.serialized["meta"] = meta
예제 #10
0
 def _update_value(self, value):
     # Attribute value might not be raw PV, PV which triggered update is
     # passed as status
     if self._user_callback is not None:
         self._user_callback(value)
     if not value.ok:
         self.attr.set_value(self.attr.value,
                             alarm=Alarm.disconnected("PV disconnected"))
     else:
         if value.severity:
             alarm = Alarm(
                 severity=value.severity,
                 status=AlarmStatus.RECORD_STATUS,
                 message="PV in alarm state",
             )
         else:
             alarm = Alarm.ok
         # We only have a raw_stamp attr on monitor, the initial
         # caget with CTRL doesn't give us a timestamp
         ts = TimeStamp(*getattr(value, "raw_stamp", (None, None)))
         value = self.attr.meta.validate(value)
         self.attr.set_value_alarm_ts(value, alarm, ts)
예제 #11
0
 def test_set_timeStamp(self):
     timeStamp = TimeStamp()
     self.o.set_timeStamp(timeStamp)
     assert self.o.timeStamp == timeStamp
예제 #12
0
 def test_bit_capture_change(self):
     ts = TimeStamp()
     changes = {"PCAP.BITS0.CAPTURE": "Value"}
     self.o.handle_changes(changes, ts)
     assert self.o.bits.value.capture == [True] * 6 + [False] * 3
예제 #13
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],
        ]
예제 #14
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",
                ],
            ],
        ]