def transition(self, state, message=""): """Change to a new state if the transition is allowed Args: state (str): State to transition to message (str): Message if the transition is to a fault state """ with self.changes_squashed: initial_state = self.state.value if self.state_set.transition_allowed(initial_state=initial_state, target_state=state): self.log.debug("%s: Transitioning from %s to %s", self.mri, initial_state, state) if state == ss.DISABLED: alarm = Alarm.invalid("Disabled") elif state == ss.FAULT: alarm = Alarm.major(message) else: alarm = Alarm() self.update_health(self, HealthInfo(alarm)) self.state.set_value(state) self.state.set_alarm(alarm) for child, writeable in self._children_writeable[state].items( ): child.meta.set_writeable(writeable) else: raise TypeError("Cannot transition from %s to %s" % (initial_state, state))
def transition(self, state, message=""): """Change to a new state if the transition is allowed Args: state (str): State to transition to message (str): Message if the transition is to a fault state """ with self.changes_squashed: initial_state = self.state.value if self.stateSet.transition_allowed( initial_state=initial_state, target_state=state): self.log.debug( "Transitioning from %s to %s", initial_state, state) if state == ss.DISABLED: alarm = Alarm.invalid("Disabled") elif state == ss.FAULT: alarm = Alarm.major(message) else: alarm = Alarm() self.update_health(self, alarm) self.state.set_value(state) self.state.set_alarm(alarm) for child, writeable in self._children_writeable[state].items(): if isinstance(child, AttributeModel): child.meta.set_writeable(writeable) elif isinstance(child, MethodModel): child.set_writeable(writeable) for element in child.takes.elements.values(): element.set_writeable(writeable) else: raise TypeError("Cannot transition from %s to %s" % (initial_state, state))
def test_set_health(self): self.update_health(1, Alarm(severity=AlarmSeverity.MINOR_ALARM)) self.update_health(2, Alarm(severity=AlarmSeverity.MAJOR_ALARM)) assert self.b.health.alarm.severity == AlarmSeverity.MAJOR_ALARM self.update_health(1, Alarm(severity=AlarmSeverity.UNDEFINED_ALARM)) self.update_health(2, Alarm(severity=AlarmSeverity.INVALID_ALARM)) assert self.b.health.alarm.severity == AlarmSeverity.UNDEFINED_ALARM self.update_health(1) self.update_health(2) assert self.o.health.value == "OK"
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)
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()
def update_part_modified(self, response): subscribe = self.config_subscriptions[response.id] name = subscribe.path[-2] original_value = self.saved_structure[name] try: np.testing.assert_equal(original_value, response.value) except AssertionError: message = "%s.%s.value = %r not %r" % ( self.name, name, response.value, original_value) if name in self.we_modified: message = "(We modified) " + message self.modified_messages[name] = message else: self.modified_messages.pop(name, None) message_list = [] only_modified_by_us = True # Tell the controller what has changed for name, message in self.modified_messages.items(): if name not in self.we_modified: only_modified_by_us = False message_list.append(message) if message_list: if only_modified_by_us: severity = AlarmSeverity.NO_ALARM else: severity = AlarmSeverity.MINOR_ALARM alarm = Alarm(severity, AlarmStatus.CONF_STATUS, "\n".join(message_list)) else: alarm = None # Put data on the queue, so if spawns are handled out of order we # still get the most up to date data self.modified_update_queue.put(alarm) self.spawn(self._update_part_modified).wait()
def __init__(self, mri: AMri, comms: AComms, publish: APublish = False) -> None: super().__init__(mri) self.comms = comms self.publish = publish self.client_comms: Optional[ClientComms] = None self.health.set_value("Uninitialized", alarm=Alarm.invalid("Uninitialized")) # Hooks self.register_hooked(ProcessStartHook, self.init)
def __init__(self, process, parts, params): self.params = params super(ProxyController, self).__init__(process, params.mri, parts) self.client_comms = process.get_controller(params.comms) self.update_health(self, Alarm.invalid("Uninitialized")) self._response_queue = Queue() self._notify_response = True self._first_response_queue = Queue()
def _update_value(self, value): # type: (Any) -> None if not value.ok or value.severity != 0: self.attr.set_value(None, alarm=Alarm.invalid("Bad PV value")) else: # Split "@asyn(PORT,num)" into ["PORT", "num"] split = value.split("(")[1].rstrip(")").split(",") cs_port = split[0].strip() cs_axis = cs_axis_names[int(split[1].strip()) - 1] self.attr.set_value("%s,%s" % (cs_port, cs_axis))
def _update_value(self, value: Any) -> None: if not value.ok: self.attr.set_value(None, alarm=Alarm.disconnected("PV Disconnected")) else: # Split "@asyn(PORT,num)" into ["PORT", "num"] split = value.split("(")[1].rstrip(")").split(",") cs_port = split[0].strip() cs_axis = CS_AXIS_NAMES[int(split[1].strip()) - 1] self.attr.set_value(f"{cs_port},{cs_axis}")
def __init__(self, mri, comms, publish=False): # type: (AMri, AComms, APublish) -> None super(ProxyController, self).__init__(mri) self.comms = comms self.publish = publish self.client_comms = None self.health.set_value("Uninitialized", alarm=Alarm.invalid("Uninitialized")) # Hooks self.register_hooked(ProcessStartHook, self.init)
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)
def _update_value(self, value, index): if index == 0: # Got CS Port if not value.ok: self.port = None elif value == 0: self.port = "" else: self.port = self.port_choices[value] elif index == 1: # Got CS Axis if value.ok and str(value) in CS_AXIS_NAMES + ["I"]: self.axis = value else: self.axis = None else: # Got PMAC Port name if value.ok: # Split "@asyn(PORT,num)" into ["PORT", "num"] split = value.split("(")[1].rstrip(")").split(",") self.pmac_attr.set_value(split[0].strip()) self.axis_num_attr.set_value(split[1].strip()) else: self.pmac_attr.set_value(None, alarm=Alarm.invalid("Bad PV value")) self.axis_num_attr.set_value( None, alarm=Alarm.invalid("Bad PV value")) if self.port is None or self.axis is None: # Bad value or PV disconnected self.cs_attr.set_value(None, alarm=Alarm.invalid("Bad PV value")) elif self.port and self.axis: # Connected to a port self.cs_attr.set_value("%s,%s" % (self.port, self.axis)) else: # Not connected to a port self.cs_attr.set_value("")
def callback(value=None): if isinstance(value, Exception): # Disconnect or Cancelled or RemoteError if isinstance(value, Disconnected): # We will get a reconnect with a whole new structure update_fields.clear() block.health.set_value( value="pvAccess disconnected", alarm=Alarm.disconnected("pvAccess disconnected")) else: with block.notifier.changes_squashed: if not update_fields: self.log.debug("Regenerating from %s", list(value)) self._regenerate_block(block, value, update_fields) done_queue.put(None) else: self._update_block(block, value, update_fields)
def handle_changes(self, changes: Dict[str, Any], ts: TimeStamp) -> None: with self.changes_squashed: icon_needs_update = False if isinstance(changes, Dict): for k, v in changes.items(): # Health changes are for us if k.upper() == "HEALTH": if v.upper() == "OK": alarm = Alarm.ok else: alarm = Alarm.major(v) self.update_health( self, builtin.infos.HealthInfo(cast(Alarm, alarm), ts) ) continue # Work out if there is a part we need to notify try: part = self.field_parts[k] except KeyError: self.log.exception(f"Can't handle field {self.block_name}.{k}") part = None if part is None: continue part.handle_change(v, ts) if not icon_needs_update: icon_needs_update = k in self.icon_part.update_fields try: mux_meta = self.mux_metas[k] except KeyError: pass else: self._handle_mux_update(mux_meta, v) if icon_needs_update: d = {} for key in self.icon_part.update_fields: if key in self.field_parts: field_part = self.field_parts[key] if field_part: d[key] = field_part.attr.value icon = builtin.util.SVGIcon(self.icon_part.svg_text) self.icon_part.update_icon(icon, d) self.icon_part.attr.set_value(str(icon), ts=ts)
def update_modified(self, part: Part = None, info: PartModifiedInfo = None) -> None: with self.changes_squashed: if part: assert info, "No info to update part" # Update the alarm for the given part self.part_modified[part] = info # Find the modified alarms for each visible part message_list = [] only_modified_by_us = True for part_name, visible in zip(self.layout.value.name, self.layout.value.visible): part = self.parts[part_name] info = self.part_modified.get(part, None) if visible and info: for name, message in sorted(info.modified.items()): # Attribute flagged as been modified, is it by the # context we passed to the part? if name in self.context_modified.get(part, {}): message = "(We modified) %s" % (message, ) else: only_modified_by_us = False message_list.append(message) # Add in any modification messages from the layout and export tables if self.layout.value.visible != self.saved_visibility: message_list.append("layout changed") only_modified_by_us = False if self.exports.value != self.saved_exports: message_list.append("exports changed") only_modified_by_us = False if message_list: if only_modified_by_us: severity = AlarmSeverity.NO_ALARM else: severity = AlarmSeverity.MINOR_ALARM alarm = Alarm(severity, AlarmStatus.CONF_STATUS, "\n".join(message_list)) self.modified.set_value(True, alarm=alarm) else: self.modified.set_value(False)
def update_modified(self, part=None, alarm=None): with self.changes_squashed: # Update the alarm for the given part if part: self.part_modified[part] = alarm # Find the modified alarms for each visible part message_list = [] only_modified_by_us = True for part_name, visible in zip( self.layout.value.name, self.layout.value.visible): if visible: alarm = self.part_modified.get(self.parts[part_name], None) if alarm: # Part flagged as been modified, is it by us? if alarm.severity: only_modified_by_us = False message_list.append(alarm.message) # Add in any modification messages from the layout and export tables try: np.testing.assert_equal( self.layout.value.visible, self.saved_visibility) except AssertionError: message_list.append("layout changed") only_modified_by_us = False try: np.testing.assert_equal( self.exports.value.to_dict(), self.saved_exports) except AssertionError: message_list.append("exports changed") only_modified_by_us = False if message_list: if only_modified_by_us: severity = AlarmSeverity.NO_ALARM else: severity = AlarmSeverity.MINOR_ALARM alarm = Alarm( severity, AlarmStatus.CONF_STATUS, "\n".join(message_list)) self.modified.set_value(True, alarm=alarm) else: self.modified.set_value(False)
def _update_value(self, value, index): if index == 0: if not value.ok: self.port = None elif value == 0: self.port = "" else: self.port = self.port_choices[value] else: if value.ok and str(value) in cs_axis_names + ["I"]: self.axis = value else: self.axis = None if self.port is None or self.axis is None: # Bad value or PV disconnected self.attr.set_value(None, alarm=Alarm.invalid("Bad PV value")) elif self.port and self.axis: # Connected to a port self.attr.set_value("%s,%s" % (self.port, self.axis)) else: # Not connected to a port self.attr.set_value("")
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
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], ]
def test_set_alarm(self): alarm = Alarm(AlarmSeverity.MAJOR_ALARM, AlarmStatus.DEVICE_STATUS, "bad") self.o.set_alarm(alarm) assert self.o.alarm == alarm
def __init__( self, mri: builtin.controllers.AMri, prefix: APvPrefix, config_dir: builtin.controllers.AConfigDir, ioc_list: AIocList = "", ) -> None: super().__init__(mri, config_dir) self.ioc = None self.ioc_blocks: OrderedDict = OrderedDict() self.prefix = prefix self.bl_iocs = ioc_list.split(" ") if self.bl_iocs[-1] == "": self.bl_iocs = self.bl_iocs[:-1] self.stats = dict() # TODO: the following stuff is all Linux-specific.... sys_call_bytes = ( open("/proc/%s/cmdline" % os.getpid(), "rb").read().split(b"\0") ) sys_call = [el.decode("utf-8") for el in sys_call_bytes] self.stats["pymalcolm_path"] = os.path.abspath(sys_call[1]) self.stats["yaml_path"] = os.path.abspath(sys_call[2]) self.stats["yaml_ver"] = self.parse_yaml_version( self.stats["yaml_path"], "/dls_sw/work", "/dls_sw/prod" ) self.stats["pymalcolm_ver"] = __version__ hostname = os.uname()[1] self.stats["kernel"] = "%s %s" % (os.uname()[0], os.uname()[2]) self.stats["hostname"] = ( hostname if len(hostname) < 39 else hostname[:35] + "..." ) self.stats["pid"] = str(os.getpid()) self.pymalcolm_path = StringMeta( "Path to pymalcolm executable", tags=[Widget.MULTILINETEXTUPDATE.tag()] ).create_attribute_model(self.stats["pymalcolm_path"]) self.pymalcolm_ver = StringMeta( "Version of pymalcolm executable", tags=[Widget.TEXTUPDATE.tag()] ).create_attribute_model(self.stats["pymalcolm_ver"]) self.yaml_path = StringMeta( "Path to yaml configuration file", tags=[Widget.MULTILINETEXTUPDATE.tag()] ).create_attribute_model(self.stats["yaml_path"]) self.yaml_ver = StringMeta( "version of yaml configuration file", tags=[Widget.TEXTUPDATE.tag()] ).create_attribute_model(self.stats["yaml_ver"]) self.hostname = StringMeta( "Name of host machine", tags=[Widget.TEXTUPDATE.tag()] ).create_attribute_model(self.stats["hostname"]) self.kernel = StringMeta( "Kernel of host machine", tags=[Widget.TEXTUPDATE.tag()] ).create_attribute_model(self.stats["kernel"]) self.pid = StringMeta( "process ID of pymalcolm instance", tags=[Widget.TEXTUPDATE.tag()] ).create_attribute_model(self.stats["pid"]) self.field_registry.add_attribute_model("pymalcolmPath", self.pymalcolm_path) self.field_registry.add_attribute_model("pymalcolmVer", self.pymalcolm_ver) self.field_registry.add_attribute_model("yamlPath", self.yaml_path) self.field_registry.add_attribute_model("yamlVer", self.yaml_ver) self.field_registry.add_attribute_model("hostname", self.hostname) self.field_registry.add_attribute_model("kernel", self.kernel) self.field_registry.add_attribute_model("pid", self.pid) if self.stats["yaml_ver"] in ["work", "unknown"]: message = "Non-prod YAML config" alarm = Alarm(message=message, severity=AlarmSeverity.MINOR_ALARM) self.update_health("", builtin.infos.HealthInfo(alarm)) self.register_hooked(ProcessStartHook, self.init) self.register_hooked(ProcessStopHook, self.stop_ioc)