Index: leahi-dialin/td/air_trap.py =================================================================== diff -u --- leahi-dialin/td/air_trap.py (revision 0) +++ leahi-dialin/td/air_trap.py (revision 873e727b657db3d14625ca0de499bfd630cbe502) @@ -0,0 +1,206 @@ +########################################################################### +# +# Copyright (c) 2020-2024 Diality Inc. - All Rights Reserved. +# +# THIS CODE MAY NOT BE COPIED OR REPRODUCED IN ANY FORM, IN PART OR IN +# WHOLE, WITHOUT THE EXPLICIT PERMISSION OF THE COPYRIGHT OWNER. +# +# @file air_trap.py +# +# @author (last) Michael Garthwaite +# @date (last) 26-Jun-2024 +# @author (original) Sean Nash +# @date (original) 21-Sep-2020 +# +############################################################################ +import struct +from logging import Logger + +from .constants import RESET, NO_RESET +from ..common.msg_defs import MsgIds, MsgFieldPositions +from ..protocols.CAN import DenaliMessage, DenaliChannels +from ..utils.base import AbstractSubSystem, publish +from ..utils.checks import check_broadcast_interval_override_ms +from ..utils.conversions import integer_to_bytearray, float_to_bytearray + + +class TDAirTrap(AbstractSubSystem): + """ + TDAirTrap + + Treatment Delivery (TD) Dialin API sub-class for air trap related commands. + """ + + # Air trap level sensor IDs + LOWER_LEVEL_SENSOR = 0 + UPPER_LEVEL_SENSOR = 1 + + # Air trap level sensor levels + AIR_DETECTED_AT_LEVEL = 0 + FLUID_DETECTED_AT_LEVEL = 1 + + def __init__(self, can_interface, logger: Logger): + """ + + @param can_interface: Denali Can Messenger object + """ + super().__init__() + self.can_interface = can_interface + self.logger = logger + + if self.can_interface is not None: + channel_id = DenaliChannels.td_sync_broadcast_ch_id + msg_id = MsgIds.MSG_ID_TD_AIR_TRAP_DATA.value + self.can_interface.register_receiving_publication_function(channel_id, msg_id, + self._handler_air_trap_sync) + + self.lower_level = self.AIR_DETECTED_AT_LEVEL + self.upper_level = self.AIR_DETECTED_AT_LEVEL + self.lower_level_raw = self.AIR_DETECTED_AT_LEVEL + self.upper_level_raw = self.AIR_DETECTED_AT_LEVEL + self.td_air_trap_timestamp = 0.0 + + @publish(["td_air_trap_timestamp", "lower_level", "upper_level", "lower_level_raw", "upper_level_raw"]) + def _handler_air_trap_sync(self, message, timestamp=0.0): + """ + Handles published air trap data messages. Air trap data are captured + for reference. + + @param message: published air trap data message + @return: None + """ + + lower = struct.unpack('i', bytearray( + message['message'][MsgFieldPositions.START_POS_FIELD_1:MsgFieldPositions.END_POS_FIELD_1])) + upper = struct.unpack('i', bytearray( + message['message'][MsgFieldPositions.START_POS_FIELD_2:MsgFieldPositions.END_POS_FIELD_2])) + raw_lower = struct.unpack('i', bytearray( + message['message'][MsgFieldPositions.START_POS_FIELD_3:MsgFieldPositions.END_POS_FIELD_3])) + raw_upper = struct.unpack('i', bytearray( + message['message'][MsgFieldPositions.START_POS_FIELD_4:MsgFieldPositions.END_POS_FIELD_4])) + + self.lower_level = lower[0] + self.upper_level = upper[0] + self.lower_level_raw = raw_lower[0] + self.upper_level_raw = raw_upper[0] + self.td_air_trap_timestamp = timestamp + + def cmd_air_trap_level_sensor_override(self, sensor: int, detected: int, reset: int = NO_RESET) -> int: + """ + Constructs and sends the air trap level sensor override command + Constraints: + Must be logged into TD. + Given sensor must be one of the sensors listed below. + + @param sensor: unsigned int - sensor ID + @param detected: unsigned int - detected (0=air, 1=fluid) to override sensor with + @param reset: integer - 1 to reset a previous override, 0 to override + @return: 1 if successful, zero otherwise + + Air trap sensor IDs: \n + 0 = Lower level \n + 1 = Upper level \n + """ + + rst = integer_to_bytearray(reset) + det = integer_to_bytearray(detected) + idx = integer_to_bytearray(sensor) + payload = rst + det + idx + + message = DenaliMessage.build_message(channel_id=DenaliChannels.dialin_to_td_ch_id, + message_id=MsgIds.MSG_ID_TD_AIR_TRAP_LEVEL_OVERRIDE_REQUEST.value, + payload=payload) + + self.logger.debug("override air trap level sensor detection value for sensor " + str(sensor) + " ,Level: " + str(detected)) + + # Send message + received_message = self.can_interface.send(message) + + # If there is content... + if received_message is not None: + # response payload is OK or not OK + return received_message['message'][DenaliMessage.PAYLOAD_START_INDEX] + else: + self.logger.debug("Timeout!!!!") + return False + + def cmd_raw_air_trap_level_sensor_override(self, sensor: int, detected: int, reset: int = NO_RESET) -> int: + """ + Constructs and sends the raw air trap level sensor override command + Constraints: + Must be logged into TD. + Given sensor must be one of the sensors listed below. + + @param sensor: unsigned int - sensor ID + @param detected: unsigned int - detected (0=air, 1=fluid) to override sensor with + @param reset: integer - 1 to reset a previous override, 0 to override + @return: 1 if successful, zero otherwise + + Air trap sensor IDs: \n + 0 = Lower level \n + 1 = Upper level \n + """ + + rst = integer_to_bytearray(reset) + det = integer_to_bytearray(detected) + idx = integer_to_bytearray(sensor) + payload = rst + det + idx + + message = DenaliMessage.build_message(channel_id=DenaliChannels.dialin_to_td_ch_id, + message_id=MsgIds.MSG_ID_TD_AIR_TRAP_LEVEL_RAW_OVERRIDE_REQUEST.value, + payload=payload) + + self.logger.debug("override raw air trap level sensor detection value for sensor " + str(sensor) + " ,Level: " + str(detected)) + + # Send message + received_message = self.can_interface.send(message) + + # If there is content... + if received_message is not None: + # response payload is OK or not OK + return received_message['message'][DenaliMessage.PAYLOAD_START_INDEX] + else: + self.logger.debug("Timeout!!!!") + return False + + def cmd_air_trap_data_broadcast_interval_override(self, ms: int, reset: int = NO_RESET) -> int: + """ + Constructs and sends the air trap data broadcast interval override command + Constraints: + Must be logged into TD. + Given interval must be non-zero and a multiple of the TD general task interval (50 ms). + + @param ms: integer - interval (in ms) to override with + @param reset: integer - 1 to reset a previous override, 0 to override + @return: 1 if successful, zero otherwise + """ + + if not check_broadcast_interval_override_ms(ms): + return False + + rst = integer_to_bytearray(reset) + mis = integer_to_bytearray(ms) + payload = rst + mis + + message = DenaliMessage.build_message(channel_id=DenaliChannels.dialin_to_td_ch_id, + message_id=MsgIds.MSG_ID_TD_AIR_TRAP_PUBLISH_INTERVAL_OVERRIDE_REQUEST.value, + payload=payload) + + self.logger.debug("override TD air trap data broadcast interval") + + # Send message + received_message = self.can_interface.send(message) + + # If there is content... + if received_message is not None: + if reset == RESET: + str_res = "reset back to normal: " + else: + str_res = str(ms) + " ms: " + self.logger.debug("Air trap data broadcast interval overridden to " + str_res + + str(received_message['message'][DenaliMessage.PAYLOAD_START_INDEX])) + # response payload is OK or not OK + return received_message['message'][DenaliMessage.PAYLOAD_START_INDEX] + else: + self.logger.debug("Timeout!!!!") + return False Index: leahi-dialin/td/alarms.py =================================================================== diff -u -r12b82879196778f6c83d08751652177ea0fcbf5e -r873e727b657db3d14625ca0de499bfd630cbe502 --- leahi-dialin/td/alarms.py (.../alarms.py) (revision 12b82879196778f6c83d08751652177ea0fcbf5e) +++ leahi-dialin/td/alarms.py (.../alarms.py) (revision 873e727b657db3d14625ca0de499bfd630cbe502) @@ -20,40 +20,40 @@ from .constants import RESET, NO_RESET from ..common.msg_defs import MsgIds, MsgFieldPositions -from ..common.td_defs import HDEventDataType +from ..common.td_defs import TDEventDataType from ..protocols.CAN import DenaliMessage, DenaliChannels from ..utils.base import AbstractSubSystem, publish from ..utils.checks import check_broadcast_interval_override_ms from ..utils.conversions import integer_to_bytearray, float_to_bytearray -class HDAlarms(AbstractSubSystem): +class TDAlarms(AbstractSubSystem): """ - HD interface containing alarm related commands. + TD interface containing alarm related commands. """ # Alarm lamp patterns - HD_ALARM_LAMP_PATTERN_OFF = 0 - HD_ALARM_LAMP_PATTERN_OK = 1 - HD_ALARM_LAMP_PATTERN_FAULT = 2 - HD_ALARM_LAMP_PATTERN_HIGH = 3 - HD_ALARM_LAMP_PATTERN_MEDIUM = 4 - HD_ALARM_LAMP_PATTERN_LOW = 5 - HD_ALARM_LAMP_PATTERN_MANUAL = 6 + TD_ALARM_LAMP_PATTERN_OFF = 0 + TD_ALARM_LAMP_PATTERN_OK = 1 + TD_ALARM_LAMP_PATTERN_FAULT = 2 + TD_ALARM_LAMP_PATTERN_HIGH = 3 + TD_ALARM_LAMP_PATTERN_MEDIUM = 4 + TD_ALARM_LAMP_PATTERN_LOW = 5 + TD_ALARM_LAMP_PATTERN_MANUAL = 6 # Alarm priority states - HD_ALARM_STATE_NONE = 0 - HD_ALARM_STATE_LOW = 1 - HD_ALARM_STATE_MEDIUM = 2 - HD_ALARM_STATE_HIGH = 3 + TD_ALARM_STATE_NONE = 0 + TD_ALARM_STATE_LOW = 1 + TD_ALARM_STATE_MEDIUM = 2 + TD_ALARM_STATE_HIGH = 3 # Alarm response buttons @unique class AlarmResponseButtons(DialinEnum): - HD_ALARM_RESPONSE_BUTTON_RESUME = 0 - HD_ALARM_RESPONSE_BUTTON_RINSEBACK = 1 - HD_ALARM_RESPONSE_BUTTON_END_TREATMENT = 2 - NUM_OF_HD_ALARM_RESPONSE_BUTTONS = 3 + TD_ALARM_RESPONSE_BUTTON_RESUME = 0 + TD_ALARM_RESPONSE_BUTTON_RINSEBACK = 1 + TD_ALARM_RESPONSE_BUTTON_END_TREATMENT = 2 + NUM_OF_TD_ALARM_RESPONSE_BUTTONS = 3 # Alarm status message field positions START_POS_ALARM_STATE = DenaliMessage.PAYLOAD_START_INDEX @@ -76,12 +76,12 @@ self.logger = logger if self.can_interface is not None: - channel_id = DenaliChannels.hd_alarm_broadcast_ch_id + channel_id = DenaliChannels.TD_alarm_broadcast_ch_id msg_id = MsgIds.MSG_ID_ALARM_STATUS_DATA.value self.can_interface.register_receiving_publication_function(channel_id, msg_id, self._handler_alarms_status_sync) - channel_id = DenaliChannels.hd_alarm_broadcast_ch_id + channel_id = DenaliChannels.TD_alarm_broadcast_ch_id msg_id = MsgIds.MSG_ID_ALARM_TRIGGERED.value self.can_interface.register_receiving_publication_function(channel_id, msg_id, self._handler_alarm_trigger) @@ -91,32 +91,32 @@ msg_id = MsgIds.MSG_ID_ALARM_CONDITION_CLEARED.value self.can_interface.register_receiving_publication_function(channel_id, msg_id, self._handler_alarm_condition_clear) - channel_id = DenaliChannels.hd_sync_broadcast_ch_id - msg_id = MsgIds.MSG_ID_HD_ALARM_INFORMATION_DATA.value + channel_id = DenaliChannels.TD_sync_broadcast_ch_id + msg_id = MsgIds.MSG_ID_TD_ALARM_INFORMATION_DATA.value self.can_interface.register_receiving_publication_function(channel_id, msg_id, self._handler_alarm_information_sync) - self.hd_alarm_status_timestamp = 0.0 - self.hd_alarm_triggered_timestamp = 0.0 - self.hd_alarm_cleared_timestamp = 0.0 - self.hd_alarm_clr_condition_timestamp = 0.0 - self.hd_alarm_information_timestamp = 0.0 - # composite alarm status based on latest HD alarm status broadcast message + self.td_alarm_status_timestamp = 0.0 + self.td_alarm_triggered_timestamp = 0.0 + self.td_alarm_cleared_timestamp = 0.0 + self.td_alarm_clr_condition_timestamp = 0.0 + self.td_alarm_information_timestamp = 0.0 + # composite alarm status based on latest TD alarm status broadcast message self.alarms_priority_state = 0 self.alarm_top = 0 self.alarms_silence_expires_in = 0 self.alarms_escalates_in = 0 self.alarms_flags = 0 - # alarm states based on received HD alarm activation and alarm clear messages + # alarm states based on received TD alarm activation and alarm clear messages self.alarm_states = [False] * 500 - # alarm condition states based on received HD alarm activation and clear condition messages + # alarm condition states based on received TD alarm activation and clear condition messages self.alarm_conditions = [False] * 500 - # alarm priorities based on received HD alarm activation messages + # alarm priorities based on received TD alarm activation messages self.alarm_priorities = [0] * 500 - # alarm ranks based on received HD alarm activation messages + # alarm ranks based on received TD alarm activation messages self.alarm_ranks = [0] * 500 - # alarm clear top only flags based on received HD alarm activation messages + # alarm clear top only flags based on received TD alarm activation messages self.alarm_clear_top_only_flags = [False] * 500 # alarm debug data on alarm triggered message self.alarm_data = [0, 0] * 500 @@ -129,12 +129,12 @@ self.alarm_backup_audio_curr = 0.0 self.safety_shutdown_active = False self.ac_power_lost = False - self.alarm_table_button_blockers = [False] * self.AlarmResponseButtons.NUM_OF_HD_ALARM_RESPONSE_BUTTONS.value - self.alarm_state_button_blockers = [False] * self.AlarmResponseButtons.NUM_OF_HD_ALARM_RESPONSE_BUTTONS.value + self.alarm_table_button_blockers = [False] * self.AlarmResponseButtons.NUM_OF_TD_ALARM_RESPONSE_BUTTONS.value + self.alarm_state_button_blockers = [False] * self.AlarmResponseButtons.NUM_OF_TD_ALARM_RESPONSE_BUTTONS.value # Loop through the list of the event data type enum and update the dictionary - for data_type in HDEventDataType: - event_data_type = HDEventDataType(data_type).name + for data_type in TDEventDataType: + event_data_type = TDEventDataType(data_type).name struct_unpack_type = None # If U32 is in the data type enum (i.e. EVENT_DATA_TYPE_U32), then the key is the enum and the value is @@ -148,302 +148,6 @@ self.alarm_data_type[event_data_type] = struct_unpack_type - def get_current_alarms_state(self): - """ - Gets the current alarms state. - - @return: (int) the current alarms state. - """ - return self.alarms_priority_state - - def get_alarm_states(self): - """ - Gets all states for all alarms - - @return: List of booleans of size 500 - """ - return self.alarm_states - - def get_alarm_response_button_table_blocker_states(self): - """ - Gets the states (T/F) of the alarm response buttons blocked by alarm table properties. - - @return: List of booleans of size 3 (for resume, rinseback and end treatment buttons) - """ - return self.alarm_table_button_blockers - - def get_alarm_response_button_state_blocker_states(self): - """ - Gets the states (T/F) of the alarm response buttons blocked by state properties. - - @return: List of booleans of size 3 (for resume, rinseback and end treatment buttons) - """ - return self.alarm_state_button_blockers - - def get_alarm_conditions(self): - """ - Gets all alarm condition states for all alarms - - @return: List of booleans of size 500 - """ - return self.alarm_conditions - - def get_alarm_state(self, alarm_id): - """ - Gets alarm state for given alarm - - @return: Alarm state - """ - return self.alarm_states[alarm_id] - - def get_alarm_priority(self, alarm_id): - """ - Gets alarm priority for given alarm. - 0=None - 1=Low - 2=Medium - 3=High - - @return: Alarm priority - """ - return self.alarm_priorities[alarm_id] - - def get_alarm_rank(self, alarm_id): - """ - Gets alarm rank for given alarm - - @return: Alarm rank - """ - return self.alarm_ranks[alarm_id] - - def get_alarm_clear_top_only(self, alarm_id): - """ - Gets clear top only property for given alarm - - @return: Clear top only property (T/F) - """ - return self.alarm_clear_top_only_flags[alarm_id] - - def get_alarms_top(self): - """ - Gets the top alarm - - @return: (int) the top alarm - """ - return self.alarm_top - - def get_alarms_silence_expires_in(self): - """ - Gets the remaining time the alarms will be silenced (s) - - @return: (int) how long until the alarm silence expires - """ - return self.alarms_silence_expires_in - - def get_alarms_escalates_in(self): - """ - Gets the alarms escalates in time (s) - - @return: (int) how long until the alarm escalates - """ - return self.alarms_escalates_in - - def get_alarms_flags(self): - """ - Gets the alarms flags - - Extract each flag from the flags int using bit-masking. E.g. - - System Fault = result & 1 - Stop = result & 2 - No Clear = result & 4 - No Resume = result & 8 - No Rinseback = result & 16 - No End Treatment = result & 32 - No New Treatment = result & 64 - User Must ACK = result & 128 - Alarms to Escalate = result & 256 - Alarms Silenced = result & 512 - Alarm Lamp On = result & 1024 - TBD = result & 2048 - No Blood Recirc = result & 4096 - No Dialysate Recirc = result & 8192 - No Minimize = result & 16384 - Condition Detected = result & 32768 - - @return: (int) The alarms flags value - """ - return self.alarms_flags - - def get_alarm_volume(self): - """ - Gets the alarm audio volume level. - - @return: (int) current alarm audio volume (1..5) - """ - return self.alarm_volume - - def get_alarm_audio_current_hg(self): - """ - Gets the alarm audio current - high gain. - - @return: (float) alarm audio current - high gain (in mA) - """ - return self.alarm_audio_curr_hg - - def get_alarm_audio_current_lg(self): - """ - Gets the alarm audio current - low gain. - - @return: (float) alarm audio current - low gain (in mA) - """ - return self.alarm_audio_curr_lg - - def get_alarm_backup_audio_current(self): - """ - Gets the alarm backup audio current. - - @return: (float) alarm backup audio current (in mA) - """ - return self.alarm_backup_audio_curr - - def get_safety_shutdown_activated(self): - """ - Gets the state of the HD safety shutdown signal. - - @return: (bool) safety shutdown line is activated (T/F) - """ - return self.safety_shutdown_active - - def get_ac_power_lost(self): - """ - Gets the state of the HD a/c power loss signal. - - @return: (bool) a/c power is lost (T/F) - """ - return self.ac_power_lost - - def get_alarm_flag_system_fault(self) -> bool: - """ - Gets the alarm flag system fault. - - @return: (bool) Alarm flag system fault (T/F) - """ - return (self.alarms_flags & 1) > 0 - - def get_alarm_flag_stop(self) -> bool: - """ - Gets the alarm flag no clear. - - @return: (bool) Alarm flag no clear (T/F) - """ - return (self.alarms_flags & 2) > 0 - - def get_alarm_flag_no_clear(self) -> bool: - """ - Gets the alarm flag no clear. - - @return: (bool) Alarm flag no clear (T/F) - """ - return (self.alarms_flags & 4) > 0 - - def get_alarm_flag_no_resume(self) -> bool: - """ - Gets the alarm flag no resume. - - @return: (bool) Alarm flag no resume (T/F) - """ - return (self.alarms_flags & 8) > 0 - - def get_alarm_flag_no_rinseback(self) -> bool: - """ - Gets the alarm flag no rinseback. - - @return: (bool) Alarm flag no rinseback (T/F) - """ - return (self.alarms_flags & 16) > 0 - - def get_alarm_flag_no_end_treatment(self) -> bool: - """ - Gets the alarm flag no end treatment. - - @return: (bool) Alarm flag no end treatment (T/F) - """ - return (self.alarms_flags & 32) > 0 - - def get_alarm_flag_no_new_treatment(self) -> bool: - """ - Gets the alarm flag no new treatment. - - @return: (bool) Alarm flag no new treatment (T/F) - """ - return (self.alarms_flags & 64) > 0 - - def get_alarm_flag_okay_button_only(self) -> bool: - """ - Gets the alarm flag Ok button only. - - @return: (bool) Alarm flag Ok button only (T/F) - """ - return (self.alarms_flags & 128) > 0 - - def get_alarm_flag_alarm_to_escalate(self) -> bool: - """ - Gets the alarm flag to escalate. - - @return: (bool) Alarm flag indicating an active alarm is due to escalate (T/F) - """ - return (self.alarms_flags & 256) > 0 - - def get_alarm_flag_is_alarm_silenced(self) -> bool: - """ - Gets the alarm flag alarm silence - - @return: (bool) Alarm flag indicating alarms are currently silenced (T/F) - """ - return (self.alarms_flags & 512) > 0 - - def get_alarm_flag_lamp_on(self) -> bool: - """ - Gets the alarm flag lamp on. - - @return: (bool) Alarm lamp on (T/F) - """ - return (self.alarms_flags & 1024) > 0 - - def get_alarm_flag_no_blood_recirculation(self) -> bool: - """ - Gets the alarm flag no blood recirculation. - - @return: (bool) Alarm lamp on (T/F) - """ - return (self.alarms_flags & 4096) > 0 - - def get_alarm_flag_no_dialysate_recirculation(self) -> bool: - """ - Gets the alarm flag no dialysate recirculation. - - @return: (bool) Alarm lamp on (T/F) - """ - return (self.alarms_flags & 8192) > 0 - - def get_alarm_flag_no_minimize(self) -> bool: - """ - Gets the alarm flag no minimize. - - @return: (bool) Alarm cannot be minimized (T/F) - """ - return (self.alarms_flags & 16384) > 0 - - def get_alarm_flag_top_condition(self) -> bool: - """ - Gets the alarm flag top condition. - - @return: (bool) Top Alarm's condition is still being detected - """ - - return (self.alarms_flags & 32768) > 0 - def clear_dialin_alarms(self): """ Clears the alarms states in Dialin. @@ -453,15 +157,7 @@ for x in range(500): self.alarm_states[x] = False - def get_alarm_data(self, alarm_id) -> list: - """ - Gets the alarm data fields for the requested alarm id - - @return: the alarm data fields for the requested alarm id - """ - return self.alarm_data[alarm_id] - - @publish(["hd_alarm_status_timestamp", "alarms_priority_state", "alarm_top", "alarms_silence_expires_in", "alarms_escalates_in", "alarms_flags"]) + @publish(["td_alarm_status_timestamp", "alarms_priority_state", "alarm_top", "alarms_silence_expires_in", "alarms_escalates_in", "alarms_flags"]) def _handler_alarms_status_sync(self, message, timestamp=0.0): """ Handles published alarms status messages. alarms status data are captured @@ -476,29 +172,29 @@ self.alarms_silence_expires_in = struct.unpack('i', bytearray(message['message'][MsgFieldPositions.START_POS_FIELD_4:MsgFieldPositions.END_POS_FIELD_4]))[0] self.alarms_flags = struct.unpack('H', bytearray(message['message'][MsgFieldPositions.START_POS_FIELD_5:MsgFieldPositions.START_POS_FIELD_5+2]))[0] - self.hd_alarm_status_timestamp = timestamp + self.td_alarm_status_timestamp = timestamp - @publish(["hd_alarm_triggered_timestamp", "alarm_states", "alarm_conditions", "alarm_data", + @publish(["td_alarm_triggered_timestamp", "alarm_states", "alarm_conditions", "alarm_data", "alarm_priorities", "alarm_ranks", "alarm_clear_top_only_flags"]) def _handler_alarm_trigger(self, message, timestamp=0.0): """ - Handles published HD alarm activation messages. + Handles published TD alarm activation messages. - @param message: published HD alarm activation message + @param message: published TD alarm activation message @return: none """ self.logger.debug("Alarm activated!") alarm_id = struct.unpack('i', bytearray(message['message'][MsgFieldPositions.START_POS_FIELD_1:MsgFieldPositions.END_POS_FIELD_1])) data_typ_1 = struct.unpack('i', bytearray(message['message'][MsgFieldPositions.START_POS_FIELD_2:MsgFieldPositions.END_POS_FIELD_2])) # Get the corresponding structure format - struct_data_type_1 = self.alarm_data_type[HDEventDataType(data_typ_1[0]).name] + struct_data_type_1 = self.alarm_data_type[TDEventDataType(data_typ_1[0]).name] # Get the data value by unpacking the data type data_1 = struct.unpack(struct_data_type_1, bytearray(message['message'][MsgFieldPositions.START_POS_FIELD_3:MsgFieldPositions.END_POS_FIELD_3])) data_typ_2 = struct.unpack('i', bytearray(message['message'][MsgFieldPositions.START_POS_FIELD_4:MsgFieldPositions.END_POS_FIELD_4])) # Get the corresponding structure format - struct_data_type_2 = self.alarm_data_type[HDEventDataType(data_typ_2[0]).name] + struct_data_type_2 = self.alarm_data_type[TDEventDataType(data_typ_2[0]).name] # Get the data value by unpacking the data type data_2 = struct.unpack(struct_data_type_2, bytearray(message['message'][MsgFieldPositions.START_POS_FIELD_5:MsgFieldPositions.END_POS_FIELD_5])) @@ -513,40 +209,40 @@ self.alarm_ranks[alarm_id[0]] = rank[0] self.alarm_clear_top_only_flags[alarm_id[0]] = clr_top_only[0] self.alarm_data[alarm_id[0]] = [data_1[0], data_2[0]] - self.hd_alarm_triggered_timestamp = timestamp + self.td_alarm_triggered_timestamp = timestamp - @publish(["hd_alarm_cleared_timestamp", "alarm_states", "alarm_conditions"]) + @publish(["TD_alarm_cleared_timestamp", "alarm_states", "alarm_conditions"]) def _handler_alarm_clear(self, message, timestamp=0.0): """ - Handles published HD alarm clear messages. + Handles published TD alarm clear messages. - @param message: published HD alarm clear message + @param message: published TD alarm clear message @return: none """ alarm_id = struct.unpack('i', bytearray(message['message'][MsgFieldPositions.START_POS_FIELD_1:MsgFieldPositions.END_POS_FIELD_1])) self.alarm_states[alarm_id[0]] = False self.alarm_conditions[alarm_id[0]] = False - self.hd_alarm_cleared_timestamp = timestamp + self.td_alarm_cleared_timestamp = timestamp - @publish(["hd_alarm_clr_condition_timestamp", "alarm_conditions", "alarm_conditions"]) + @publish(["td_alarm_clr_condition_timestamp", "alarm_conditions", "alarm_conditions"]) def _handler_alarm_condition_clear(self, message, timestamp=0.0): """ - Handles published HD alarm clear alarm condition messages. + Handles published TD alarm clear alarm condition messages. - @param message: published HD alarm clear alarm condition message + @param message: published TD alarm clear alarm condition message @return: none """ alarm_id = struct.unpack('i', bytearray(message['message'][MsgFieldPositions.START_POS_FIELD_1:MsgFieldPositions.END_POS_FIELD_1])) self.alarm_conditions[alarm_id[0]] = False - self.hd_alarm_clr_condition_timestamp = timestamp + self.td_alarm_clr_condition_timestamp = timestamp - @publish(["hd_alarm_information_timestamp", "alarm_volume", "alarm_audio_curr_hg", "alarm_audio_curr_lg", "alarm_backup_audio_curr", + @publish(["td_alarm_information_timestamp", "alarm_volume", "alarm_audio_curr_hg", "alarm_audio_curr_lg", "alarm_backup_audio_curr", "safety_shutdown_active", "ac_power_lost", "alarm_table_button_blockers", "alarm_state_button_blockers"]) def _handler_alarm_information_sync(self, message, timestamp=0.0): """ - Handles published HD alarm information broadcast messages. + Handles published TD alarm information broadcast messages. - @param message: published HD alarm information message + @param message: published TD alarm information message @return: none """ @@ -581,69 +277,19 @@ self.alarm_backup_audio_curr = bac[0] self.safety_shutdown_active = True if saf[0] == 1 else False self.ac_power_lost = True if acp[0] == 1 else False - self.alarm_table_button_blockers[self.AlarmResponseButtons.HD_ALARM_RESPONSE_BUTTON_RESUME.value] = True if trs[0] == 1 else False - self.alarm_table_button_blockers[self.AlarmResponseButtons.HD_ALARM_RESPONSE_BUTTON_RINSEBACK.value] = True if trb[0] else False - self.alarm_table_button_blockers[self.AlarmResponseButtons.HD_ALARM_RESPONSE_BUTTON_END_TREATMENT.value] = True if tet[0] else False - self.alarm_state_button_blockers[self.AlarmResponseButtons.HD_ALARM_RESPONSE_BUTTON_RESUME.value] = True if srs[0] else False - self.alarm_state_button_blockers[self.AlarmResponseButtons.HD_ALARM_RESPONSE_BUTTON_RINSEBACK.value] = True if srb[0] else False - self.alarm_state_button_blockers[self.AlarmResponseButtons.HD_ALARM_RESPONSE_BUTTON_END_TREATMENT.value] = True if set[0] else False - self.hd_alarm_information_timestamp = timestamp + self.alarm_table_button_blockers[self.AlarmResponseButtons.TD_ALARM_RESPONSE_BUTTON_RESUME.value] = True if trs[0] == 1 else False + self.alarm_table_button_blockers[self.AlarmResponseButtons.TD_ALARM_RESPONSE_BUTTON_RINSEBACK.value] = True if trb[0] else False + self.alarm_table_button_blockers[self.AlarmResponseButtons.TD_ALARM_RESPONSE_BUTTON_END_TREATMENT.value] = True if tet[0] else False + self.alarm_state_button_blockers[self.AlarmResponseButtons.TD_ALARM_RESPONSE_BUTTON_RESUME.value] = True if srs[0] else False + self.alarm_state_button_blockers[self.AlarmResponseButtons.TD_ALARM_RESPONSE_BUTTON_RINSEBACK.value] = True if srb[0] else False + self.alarm_state_button_blockers[self.AlarmResponseButtons.TD_ALARM_RESPONSE_BUTTON_END_TREATMENT.value] = True if set[0] else False + self.td_alarm_information_timestamp = timestamp - def cmd_clear_all_alarms(self) -> int: - """ - Constructs and sends the clear all active alarms command. - This will clear even non-recoverable alarms. - Constraints: - Must be logged into HD. - - @return: 1 if successful, zero otherwise - """ - - key = integer_to_bytearray(-758926171) # 0xD2C3B4A5 - payload = key - - message = DenaliMessage.build_message(channel_id=DenaliChannels.dialin_to_hd_ch_id, - message_id=MsgIds.MSG_ID_SUPER_CLEAR_ALARMS_CMD.value, - payload=payload) - - # Send message - received_message = self.can_interface.send(message) - - # If there is content... - if received_message is not None: - self.logger.debug("All alarms cleared.") - # response payload is OK or not OK - return 1 == received_message['message'][DenaliMessage.PAYLOAD_START_INDEX] - return False - - def cmd_resend_all_alarms(self) -> int: - """ - Constructs and sends the re-send all active alarms command. - This will allow Dialin to get caught up with HD alarms that were triggered prior to connection. - Constraints: - Must be logged into HD. - - @return: 1 if successful, zero otherwise - """ - - message = DenaliMessage.build_message(channel_id=DenaliChannels.dialin_to_hd_ch_id, - message_id=MsgIds.MSG_ID_HD_SEND_ALARMS_COMMAND.value) - - # Send message - received_message = self.can_interface.send(message) - - # If there is content... - if received_message is not None: - self.logger.debug("Command to re-send all active HD alarms acknowledged.") - # response payload is OK or not OK - return 1 == received_message['message'][DenaliMessage.PAYLOAD_START_INDEX] - return False - def cmd_alarm_state_override(self, alarm: int, state: int, reset: int = NO_RESET) -> int: """ Constructs and sends the alarm state override command Constraints: - Must be logged into HD. + Must be logged into TD. Given alarm must be valid. If inactivating alarm, given alarm must be recoverable (clearable). @@ -658,8 +304,8 @@ alm = integer_to_bytearray(alarm) payload = rst + sta + alm - message = DenaliMessage.build_message(channel_id=DenaliChannels.dialin_to_hd_ch_id, - message_id=MsgIds.MSG_ID_ALARM_STATE_OVERRIDE.value, + message = DenaliMessage.build_message(channel_id=DenaliChannels.dialin_to_td_ch_id, + message_id=MsgIds.MSG_ID_TD_ALARM_STATE_OVERRIDE_REQUEST.value, payload=payload) # Send message @@ -680,11 +326,38 @@ self.logger.debug("Timeout!!!!") return False + def cmd_clear_all_alarms(self) -> int: + """ + Constructs and sends the clear all active alarms command. + This will clear even non-recoverable alarms. + Constraints: + Must be logged into TD. + + @return: 1 if successful, zero otherwise + """ + + key = integer_to_bytearray(-758926171) # 0xD2C3B4A5 + payload = key + + message = DenaliMessage.build_message(channel_id=DenaliChannels.dialin_to_td_ch_id, + message_id=MsgIds.MSG_ID_TD_ALARM_CLEAR_ALL_ALARMS_REQUEST.value, + payload=payload) + + # Send message + received_message = self.can_interface.send(message) + + # If there is content... + if received_message is not None: + self.logger.debug("All alarms cleared.") + # response payload is OK or not OK + return 1 == received_message['message'][DenaliMessage.PAYLOAD_START_INDEX] + return False + def cmd_alarm_time_override(self, alarm: int, time_ms: int, reset: int = NO_RESET) -> int: """ Constructs and sends the alarm time override command Constraints: - Must be logged into HD. + Must be logged into TD. Given alarm must be valid. @param alarm: integer - ID of alarm to override @@ -698,8 +371,8 @@ alm = integer_to_bytearray(alarm) payload = rst + ms + alm - message = DenaliMessage.build_message(channel_id=DenaliChannels.dialin_to_hd_ch_id, - message_id=MsgIds.MSG_ID_ALARM_TIME_OVERRIDE.value, + message = DenaliMessage.build_message(channel_id=DenaliChannels.dialin_to_td_ch_id, + message_id=MsgIds.MSG_ID_TD_ALARM_START_TIME_OVERRIDE_REQUEST.value, payload=payload) self.logger.debug("override alarm time since activated") @@ -726,7 +399,7 @@ """ Constructs and sends the alarm lamp pattern override command. Constraints: - Must be logged into HD. + Must be logged into TD. Given pattern must be one of the patterns listed below. @param pattern: integer - ID of alarm lamp pattern to override with @@ -746,8 +419,8 @@ pat = integer_to_bytearray(pattern) payload = rst + pat - message = DenaliMessage.build_message(channel_id=DenaliChannels.dialin_to_hd_ch_id, - message_id=MsgIds.MSG_ID_ALARM_LAMP_PATTERN_OVERRIDE.value, + message = DenaliMessage.build_message(channel_id=DenaliChannels.dialin_to_td_ch_id, + message_id=MsgIds.MSG_ID_TD_ALARM_LAMP_PATTERN_OVERRIDE_REQUEST.value, payload=payload) self.logger.debug("Override Alarm Lamp Pattern") @@ -760,17 +433,17 @@ self.logger.debug(received_message) if reset == RESET: str_pat = "reset back to normal" - elif pattern == self.HD_ALARM_LAMP_PATTERN_OFF: + elif pattern == self.TD_ALARM_LAMP_PATTERN_OFF: str_pat = "off" - elif pattern == self.HD_ALARM_LAMP_PATTERN_OK: + elif pattern == self.TD_ALARM_LAMP_PATTERN_OK: str_pat = "ok" - elif pattern == self.HD_ALARM_LAMP_PATTERN_FAULT: + elif pattern == self.TD_ALARM_LAMP_PATTERN_FAULT: str_pat = "fault" - elif pattern == self.HD_ALARM_LAMP_PATTERN_HIGH: + elif pattern == self.TD_ALARM_LAMP_PATTERN_HIGH: str_pat = "high" - elif pattern == self.HD_ALARM_LAMP_PATTERN_MEDIUM: + elif pattern == self.TD_ALARM_LAMP_PATTERN_MEDIUM: str_pat = "medium" - elif pattern == self.HD_ALARM_LAMP_PATTERN_LOW: + elif pattern == self.TD_ALARM_LAMP_PATTERN_LOW: str_pat = "low" else: str_pat = "manual" @@ -786,8 +459,8 @@ """ Constructs and sends the alarm information broadcast interval override command Constraints: - Must be logged into HD. - Given interval must be non-zero and a multiple of the HD general task interval (50 ms). + Must be logged into TD. + Given interval must be non-zero and a multiple of the TD general task interval (50 ms). @param ms: integer - interval (in ms) to override with @param reset: integer - 1 to reset a previous override, 0 to override @@ -801,8 +474,8 @@ mis = integer_to_bytearray(ms) payload = rst + mis - message = DenaliMessage.build_message(channel_id=DenaliChannels.dialin_to_hd_ch_id, - message_id=MsgIds.MSG_ID_HD_ALARM_INFO_SEND_INTERVAL_OVERRIDE.value, + message = DenaliMessage.build_message(channel_id=DenaliChannels.dialin_to_td_ch_id, + message_id=MsgIds.MSG_ID_TD_ALARM_INFO_PUBLISH_INTERVAL_OVERRIDE_REQUEST.value, payload=payload) self.logger.debug("override alarm information broadcast interval") @@ -828,8 +501,8 @@ """ Constructs and sends the alarm status broadcast interval override command Constraints: - Must be logged into HD. - Given interval must be non-zero and a multiple of the HD general task interval (50 ms). + Must be logged into TD. + Given interval must be non-zero and a multiple of the TD general task interval (50 ms). @param ms: integer - interval (in ms) to override with @param reset: integer - 1 to reset a previous override, 0 to override @@ -843,8 +516,8 @@ mis = integer_to_bytearray(ms) payload = rst + mis - message = DenaliMessage.build_message(channel_id=DenaliChannels.dialin_to_hd_ch_id, - message_id=MsgIds.MSG_ID_HD_ALARM_STATUS_PUBLISH_INTERVAL_OVERRIDE.value, + message = DenaliMessage.build_message(channel_id=DenaliChannels.dialin_to_td_ch_id, + message_id=MsgIds.MSG_ID_TD_ALARM_STATUS_PUBLISH_INTERVAL_OVERRIDE_REQUEST.value, payload=payload) self.logger.debug("override alarm status broadcast interval") @@ -866,11 +539,11 @@ self.logger.debug("Timeout!!!!") return False - def cmd_alarm_audio_volume_override(self, volume: int = 5, reset: int = NO_RESET): + def cmd_alarm_audio_level_override(self, volume: int = 5, reset: int = NO_RESET): """ Constructs and sends the alarm audio volume override command Constraints: - Must be logged into HD. + Must be logged into TD. Given volume must be an integer between 1 and 5. @param volume: integer - alarm audio volume level (1..5) @@ -882,8 +555,8 @@ vol = integer_to_bytearray(volume) payload = rst + vol - message = DenaliMessage.build_message(channel_id=DenaliChannels.dialin_to_hd_ch_id, - message_id=MsgIds.MSG_ID_HD_ALARM_AUDIO_VOLUME_LEVEL_OVERRIDE.value, + message = DenaliMessage.build_message(channel_id=DenaliChannels.dialin_to_td_ch_id, + message_id=MsgIds.MSG_ID_TD_ALARM_AUDIO_LEVEL_OVERRIDE_REQUEST.value, payload=payload) self.logger.debug("override alarm audio volume level") @@ -909,7 +582,7 @@ """ Constructs and sends the alarm audio current (high gain) override command Constraints: - Must be logged into HD. + Must be logged into TD. @param current: float - current (in mA) for high gain alarm audio @param reset: integer - 1 to reset a previous override, 0 to override @@ -920,8 +593,8 @@ cur = float_to_bytearray(current) payload = rst + cur - message = DenaliMessage.build_message(channel_id=DenaliChannels.dialin_to_hd_ch_id, - message_id=MsgIds.MSG_ID_HD_ALARM_AUDIO_CURRENT_HG_OVERRIDE.value, + message = DenaliMessage.build_message(channel_id=DenaliChannels.dialin_to_td_ch_id, + message_id=MsgIds.MSG_ID_TD_ALARM_AUDIO_CURRENT_HG_OVERRIDE_REQUEST.value, payload=payload) self.logger.debug("override alarm audio high gain current") @@ -947,7 +620,7 @@ """ Constructs and sends the alarm audio current (low gain) override command Constraints: - Must be logged into HD. + Must be logged into TD. @param current: float - current (in mA) for low gain alarm audio @param reset: integer - 1 to reset a previous override, 0 to override @@ -958,8 +631,8 @@ cur = float_to_bytearray(current) payload = rst + cur - message = DenaliMessage.build_message(channel_id=DenaliChannels.dialin_to_hd_ch_id, - message_id=MsgIds.MSG_ID_HD_ALARM_AUDIO_CURRENT_LG_OVERRIDE.value, + message = DenaliMessage.build_message(channel_id=DenaliChannels.dialin_to_td_ch_id, + message_id=MsgIds.MSG_ID_TD_ALARM_AUDIO_CURRENT_LG_OVERRIDE_REQUEST.value, payload=payload) self.logger.debug("override alarm audio high gain current") @@ -985,7 +658,7 @@ """ Constructs and sends the backup alarm audio current override command Constraints: - Must be logged into HD. + Must be logged into TD. @param current: float - current (in mA) for backup alarm audio @param reset: integer - 1 to reset a previous override, 0 to override @@ -996,8 +669,8 @@ cur = float_to_bytearray(current) payload = rst + cur - message = DenaliMessage.build_message(channel_id=DenaliChannels.dialin_to_hd_ch_id, - message_id=MsgIds.MSG_ID_HD_ALARM_BACKUP_AUDIO_CURRENT_OVERRIDE.value, + message = DenaliMessage.build_message(channel_id=DenaliChannels.dialin_to_td_ch_id, + message_id=MsgIds.MSG_ID_TD_BACKUP_ALARM_AUDIO_CURRENT_OVERRIDE_REQUEST.value, payload=payload) self.logger.debug("override alarm backup audio current") Index: leahi-dialin/td/blood_flow.py =================================================================== diff -u --- leahi-dialin/td/blood_flow.py (revision 0) +++ leahi-dialin/td/blood_flow.py (revision 873e727b657db3d14625ca0de499bfd630cbe502) @@ -0,0 +1,429 @@ +########################################################################### +# +# Copyright (c) 2020-2024 Diality Inc. - All Rights Reserved. +# +# THIS CODE MAY NOT BE COPIED OR REPRODUCED IN ANY FORM, IN PART OR IN +# WHOLE, WITHOUT THE EXPLICIT PERMISSION OF THE COPYRIGHT OWNER. +# +# @file blood_flow.py +# +# @author (last) Micahel Garthwaite +# @date (last) 17-Aug-2023 +# @author (original) Peter Lucia +# @date (original) 02-Apr-2020 +# +############################################################################ +import struct +from logging import Logger + +from .constants import PUMP_CONTROL_MODE_CLOSED_LOOP, PUMP_CONTROL_MODE_OPEN_LOOP +from .constants import RESET, NO_RESET +from ..common.msg_defs import MsgIds, MsgFieldPositions +from ..protocols.CAN import DenaliMessage, DenaliChannels +from ..utils.base import AbstractSubSystem, publish +from ..utils.checks import check_broadcast_interval_override_ms +from ..utils.conversions import integer_to_bytearray, float_to_bytearray + + +class TDBloodFlow(AbstractSubSystem): + """ + Treatment Device (TD) Dialin API sub-class for blood-flow related commands. + """ + + def __init__(self, can_interface, logger: Logger): + """ + TDBloodFlow constructor + """ + super().__init__() + self.can_interface = can_interface + self.logger = logger + + if self.can_interface is not None: + channel_id = DenaliChannels.td_sync_broadcast_ch_id + msg_id = MsgIds.MSG_ID_TD_BLOOD_PUMP_DATA.value + self.can_interface.register_receiving_publication_function(channel_id, msg_id, + self._handler_blood_flow_sync) + self.td_blood_flow_timestamp = 0.0 + self.set_blood_flow_rate = 0 + self.measured_blood_flow_rate = 0.0 + self.measured_blood_pump_rotor_speed = 0.0 + self.measured_blood_pump_speed = 0.0 + self.measured_blood_pump_motor_speed = 0.0 + self.measured_blood_pump_motor_current = 0.0 + self.rotor_count = 0 + self.pres_blood_flow_rate = 0 + self.rotor_hall_state = 0 + + + @publish(["td_blood_flow_timestamp", "target_blood_flow_rate", "measured_blood_flow_rate", "measured_blood_pump_rotor_speed", + "measured_blood_pump_speed", "measured_blood_pump_mc_speed", "measured_blood_pump_mc_current", + "rotor_count", "pres_blood_flow_rate", "rotor_hall_state"]) + def _handler_blood_flow_sync(self, message, timestamp=0.0): + """ + Handles published blood flow data messages. Blood flow data are captured + for reference. + + @param message: published blood flow data message + @return: none + """ + + tgt = struct.unpack('i', bytearray( + message['message'][MsgFieldPositions.START_POS_FIELD_1:MsgFieldPositions.END_POS_FIELD_1])) + flow = struct.unpack('f', bytearray( + message['message'][MsgFieldPositions.START_POS_FIELD_2:MsgFieldPositions.END_POS_FIELD_2])) + rotor = struct.unpack('f', bytearray( + message['message'][MsgFieldPositions.START_POS_FIELD_3:MsgFieldPositions.END_POS_FIELD_3])) + speed = struct.unpack('f', bytearray( + message['message'][MsgFieldPositions.START_POS_FIELD_4:MsgFieldPositions.END_POS_FIELD_4])) + mcspeed = struct.unpack('f', bytearray( + message['message'][MsgFieldPositions.START_POS_FIELD_5:MsgFieldPositions.END_POS_FIELD_5])) + mccurr = struct.unpack('f', bytearray( + message['message'][MsgFieldPositions.START_POS_FIELD_6:MsgFieldPositions.END_POS_FIELD_6])) + rot = struct.unpack('I', bytearray( + message['message'][MsgFieldPositions.START_POS_FIELD_8:MsgFieldPositions.END_POS_FIELD_7])) + pres = struct.unpack('I', bytearray( + message['message'][MsgFieldPositions.START_POS_FIELD_9:MsgFieldPositions.END_POS_FIELD_8])) + hal = struct.unpack('I', bytearray( + message['message'][MsgFieldPositions.START_POS_FIELD_10:MsgFieldPositions.END_POS_FIELD_9])) + + self.target_blood_flow_rate = tgt[0] + self.measured_blood_flow_rate = flow[0] + self.measured_blood_pump_rotor_speed = rotor[0] + self.measured_blood_pump_speed = speed[0] + self.measured_blood_pump_motor_speed = mcspeed[0] + self.measured_blood_pump_motor_current = mccurr[0] + self.rotor_count = rot[0] + self.pres_blood_flow_rate = pres[0] + self.rotor_hall_state = hal[0] + self.td_blood_flow_timestamp = timestamp + + def cmd_blood_flow_set_point_override(self, flow: int, mode: int = PUMP_CONTROL_MODE_CLOSED_LOOP, + reset: int = NO_RESET) -> int: + """ + Constructs and sends the blood flow set point override command + Constraints: + Must be logged into TD. + Given flow rate must be valid (<= +/-500 mL/min). + + @param flow: integer - flow set point (in mL/min) to override with (negative for reverse direction) + @param mode: integer - 0 for closed loop control mode or 1 for open loop control mode + @param reset: integer - N/A + @return: 1 if successful, zero otherwise + """ + + rst = integer_to_bytearray(reset) + flo = integer_to_bytearray(flow) + mod = integer_to_bytearray(mode) + payload = rst + flo + mod + + message = DenaliMessage.build_message(channel_id=DenaliChannels.dialin_to_td_ch_id, + message_id=MsgIds.MSG_ID_BLOOD_FLOW_SET_PT_OVERRIDE.value, + payload=payload) + + self.logger.debug("override blood flow set point") + + # Send message + received_message = self.can_interface.send(message) + + # If there is content... + if received_message is not None: + # self.logger.debug(received_message) + if reset == RESET: + str_res = "reset back to normal" + else: + str_res = str(flow) + + if mode == PUMP_CONTROL_MODE_OPEN_LOOP: + str_mode = " (open loop): " + else: + str_mode = " (closed loop): " + self.logger.debug( + "Blood flow set point overridden to " + str_res + " mL/min" + str_mode + + + str(received_message['message'][DenaliMessage.PAYLOAD_START_INDEX])) + # response payload is OK or not OK + return received_message['message'][DenaliMessage.PAYLOAD_START_INDEX] + else: + self.logger.debug("Timeout!!!!") + return False + + def cmd_blood_flow_measured_override(self, flow: int, reset: int = NO_RESET) -> int: + """ + Constructs and sends the measured blood flow override command + Constraints: + Must be logged into TD. + + @param flow: integer - measured flow (in mL/min) to override with + @param reset: integer - 1 to reset a previous override, 0 to override + @return: 1 if successful, zero otherwise + """ + + rst = integer_to_bytearray(reset) + flo = float_to_bytearray(flow) + payload = rst + flo + + message = DenaliMessage.build_message(channel_id=DenaliChannels.dialin_to_td_ch_id, + message_id=MsgIds.MSG_ID_BLOOD_FLOW_MEAS_OVERRIDE.value, + payload=payload) + + self.logger.debug("override measured blood flow") + + # Send message + received_message = self.can_interface.send(message) + + # If there is content... + if received_message is not None: + # self.logger.debug(received_message) + if reset == RESET: + str_res = "reset back to normal" + else: + str_res = str(flow) + self.logger.debug("Blood flow (measured)) overridden to " + str_res + " mL/min: " + + str(received_message['message'][DenaliMessage.PAYLOAD_START_INDEX])) + # response payload is OK or not OK + return received_message['message'][DenaliMessage.PAYLOAD_START_INDEX] + else: + self.logger.debug("Timeout!!!!") + return False + + def cmd_blood_pump_measured_motor_controller_speed_override(self, speed: int, reset: int = NO_RESET) -> int: + """ + Constructs and sends the measured blood pump motor controller speed \n + override command. + Constraints: + Must be logged into TD. + + @param speed: integer - speed (in RPM) to override with + @param reset: integer - 1 to reset a previous override, 0 to override + @return: 1 if successful, zero otherwise + """ + + rst = integer_to_bytearray(reset) + spd = float_to_bytearray(speed) + payload = rst + spd + + message = DenaliMessage.build_message(channel_id=DenaliChannels.dialin_to_td_ch_id, + message_id=MsgIds.MSG_ID_BLOOD_PUMP_MC_MEAS_SPEED_OVERRIDE.value, + payload=payload) + + self.logger.debug("override measured blood pump motor controller speed") + + # Send message + received_message = self.can_interface.send(message) + + # If there is content... + if received_message is not None: + # self.logger.debug(received_message) + if reset == RESET: + str_res = "reset back to normal" + else: + str_res = str(speed) + self.logger.debug("Blood pump MC speed (measured) overridden to " + str_res + " RPM: " + + str(received_message['message'][DenaliMessage.PAYLOAD_START_INDEX])) + # response payload is OK or not OK + return received_message['message'][DenaliMessage.PAYLOAD_START_INDEX] + else: + self.logger.debug("Timeout!!!!") + return False + + def cmd_blood_pump_measured_motor_controller_current_override(self, curr: float, reset: int = NO_RESET) -> int: + """ + Constructs and sends the measured blood pump motor controller current override command + Constraints: + Must be logged into TD. + + @param curr: float - current (in mA) to override with + @param reset: integer - 1 to reset a previous override, 0 to override + @return: 1 if successful, zero otherwise + """ + + rst = integer_to_bytearray(reset) + cur = float_to_bytearray(curr) + payload = rst + cur + + message = DenaliMessage.build_message(channel_id=DenaliChannels.dialin_to_td_ch_id, + message_id=MsgIds.MSG_ID_BLOOD_PUMP_MC_MEAS_CURR_OVERRIDE.value, + payload=payload) + + self.logger.debug("override measured blood pump motor controller current") + + # Send message + received_message = self.can_interface.send(message) + + # If there is content... + if received_message is not None: + # self.logger.debug(received_message) + if reset == RESET: + str_res = "reset back to normal" + else: + str_res = str(curr) + self.logger.debug("Blood pump MC current (measured) overridden to " + str_res + " mA: " + + str(received_message['message'][DenaliMessage.PAYLOAD_START_INDEX])) + # response payload is OK or not OK + return received_message['message'][DenaliMessage.PAYLOAD_START_INDEX] + else: + self.logger.debug("Timeout!!!!") + return False + + def cmd_blood_pump_measured_motor_speed_override(self, speed: int, reset: int = NO_RESET) -> int: + """ + Constructs and sends the measured blood pump motor speed override \n + command. + Constraints: + Must be logged into TD. + + @param speed: integer - speed (in RPM) to override with + @param reset: integer - 1 to reset a previous override, 0 to override + @return: 1 if successful, zero otherwise + """ + + rst = integer_to_bytearray(reset) + spd = float_to_bytearray(speed) + payload = rst + spd + + message = DenaliMessage.build_message(channel_id=DenaliChannels.dialin_to_td_ch_id, + message_id=MsgIds.MSG_ID_BLOOD_PUMP_MEAS_SPEED_OVERRIDE.value, + payload=payload) + + self.logger.debug("override measured blood pump speed") + + # Send message + received_message = self.can_interface.send(message) + + # If there is content... + if received_message is not None: + # self.logger.debug(received_message) + if reset == RESET: + str_res = "reset back to normal" + else: + str_res = str(speed) + self.logger.debug("Blood pump speed (measured) overridden to " + str_res + " RPM: " + + str(received_message['message'][DenaliMessage.PAYLOAD_START_INDEX])) + # response payload is OK or not OK + return received_message['message'][DenaliMessage.PAYLOAD_START_INDEX] + else: + self.logger.debug("Timeout!!!!") + return False + + def cmd_blood_pump_measured_rotor_speed_override(self, speed: int, reset: int = NO_RESET) -> int: + """ + Constructs and sends the measured blood pump rotor speed override \n + command. + Constraints: + Must be logged into TD. + + @param speed: integer - speed (in RPM) to override with + @param reset: integer - 1 to reset a previous override, 0 to override + @return: 1 if successful, zero otherwise + """ + + rst = integer_to_bytearray(reset) + spd = float_to_bytearray(speed) + payload = rst + spd + + message = DenaliMessage.build_message(channel_id=DenaliChannels.dialin_to_td_ch_id, + message_id=MsgIds.MSG_ID_BLOOD_PUMP_MEAS_ROTOR_SPEED_OVERRIDE.value, + payload=payload) + + self.logger.debug("override measured blood pump rotor speed") + + # Send message + received_message = self.can_interface.send(message) + + # If there is content... + if received_message is not None: + # self.logger.debug(received_message) + if reset == RESET: + str_res = "reset back to normal" + else: + str_res = str(speed) + self.logger.debug("Blood pump rotor speed (measured) overridden to " + str_res + " RPM: " + + str(received_message['message'][DenaliMessage.PAYLOAD_START_INDEX])) + # response payload is OK or not OK + return received_message['message'][DenaliMessage.PAYLOAD_START_INDEX] + else: + self.logger.debug("Timeout!!!!") + return False + + def cmd_blood_flow_broadcast_interval_override(self, ms: int, reset: int = NO_RESET) -> int: + """ + Constructs and sends the measured blood flow broadcast interval override command + Constraints: + Must be logged into TD. + Given interval must be non-zero and a multiple of the TD general task interval (50 ms). + + @param ms: integer - interval (in ms) to override with + @param reset: integer - 1 to reset a previous override, 0 to override + @return: 1 if successful, zero otherwise + """ + + if not check_broadcast_interval_override_ms(ms): + return False + + rst = integer_to_bytearray(reset) + mis = integer_to_bytearray(ms) + payload = rst + mis + + message = DenaliMessage.build_message(channel_id=DenaliChannels.dialin_to_td_ch_id, + message_id=MsgIds.MSG_ID_BLOOD_FLOW_SEND_INTERVAL_OVERRIDE.value, + payload=payload) + + self.logger.debug("override blood flow broadcast interval") + + # Send message + received_message = self.can_interface.send(message) + + # If there is content... + if received_message is not None: + # self.logger.debug(received_message) + if reset == RESET: + str_res = "reset back to normal: " + else: + str_res = str(ms) + " ms: " + self.logger.debug("Blood flow broadcast interval overridden to " + str_res + + str(received_message['message'][DenaliMessage.PAYLOAD_START_INDEX])) + # response payload is OK or not OK + return received_message['message'][DenaliMessage.PAYLOAD_START_INDEX] + else: + self.logger.debug("Timeout!!!!") + return False + + def cmd_blood_pump_rotor_count_override(self, rot_count: int, reset: int = NO_RESET) -> int: + """ + Constructs and sends the blood pump rotor count override command + Constraints: + Must be logged into TD. + Given count must be zero or positive integer. + + @param rot_count: integer - rotor count to override with + @param reset: integer - 1 to reset a previous override, 0 to override + @return: 1 if successful, zero otherwise + """ + + rst = integer_to_bytearray(reset) + cnt = integer_to_bytearray(rot_count) + payload = rst + cnt + + message = DenaliMessage.build_message(channel_id=DenaliChannels.dialin_to_td_ch_id, + message_id=MsgIds.MSG_ID_BLOOD_ROTOR_COUNT_OVERRIDE.value, + payload=payload) + + self.logger.debug("override blood pump rotor count") + + # Send message + received_message = self.can_interface.send(message) + + # If there is content... + if received_message is not None: + # self.logger.debug(received_message) + if reset == RESET: + str_res = "reset back to normal: " + else: + str_res = str(rot_count) + ": " + self.logger.debug("Blood pump rotor count overridden to " + str_res + + str(received_message['message'][DenaliMessage.PAYLOAD_START_INDEX])) + # response payload is OK or not OK + return received_message['message'][DenaliMessage.PAYLOAD_START_INDEX] + else: + self.logger.debug("Timeout!!!!") + return False Index: leahi-dialin/td/switches.py =================================================================== diff -u -r12b82879196778f6c83d08751652177ea0fcbf5e -r873e727b657db3d14625ca0de499bfd630cbe502 --- leahi-dialin/td/switches.py (.../switches.py) (revision 12b82879196778f6c83d08751652177ea0fcbf5e) +++ leahi-dialin/td/switches.py (.../switches.py) (revision 873e727b657db3d14625ca0de499bfd630cbe502) @@ -1 +1,161 @@ +########################################################################### +# +# Copyright (c) 2021-2024 Diality Inc. - All Rights Reserved. +# +# THIS CODE MAY NOT BE COPIED OR REPRODUCED IN ANY FORM, IN PART OR IN +# WHOLE, WITHOUT THE EXPLICIT PERMISSION OF THE COPYRIGHT OWNER. +# +# @file switches.py +# +# @author (last) Micahel Garthwaite +# @date (last) 03-Mar-2023 +# @author (original) Dara Navaei +# @date (original) 25-Jul-2021 +# +############################################################################ + +import struct +from enum import unique +from logging import Logger + +from ..utils.conversions import integer_to_bytearray +from ..common.msg_defs import MsgIds, MsgFieldPositions +from .constants import RESET, NO_RESET +from ..protocols.CAN import DenaliMessage, DenaliChannels +from ..utils.base import AbstractSubSystem, publish, DialinEnum +from ..utils.checks import check_broadcast_interval_override_ms + + +@unique +class TDSwitchStatus(DialinEnum): + CLOSED = 0 + OPEN = 1 + + + +@unique +class TDSwitchesNames(DialinEnum): + FRONT_DOOR = 0 + NUM_OF_DOORS_AND_SWITCHES = 1 + + +class TDSwitches(AbstractSubSystem): + """ + @brief Treatment Device (TD) Dialin API sub-class for TD switches related commands. + """ + + def __init__(self, can_interface, logger: Logger): + """ + TDSwitches constructor + + """ + super().__init__() + + self.can_interface = can_interface + self.logger = logger + + if self.can_interface is not None: + channel_id = DenaliChannels.TD_sync_broadcast_ch_id + msg_id = MsgIds.MSG_ID_TD_SWITCHES_DATA.value + self.can_interface.register_receiving_publication_function(channel_id, msg_id, + self._handler_switches_sync) + + self.td_switches_status = {TDSwitchesNames.FRONT_DOOR.name: TDSwitchStatus.CLOSED.value, + TDSwitchesNames.PUMP_TRACK_SWITCH.name: TDSwitchStatus.CLOSED.value} + self.td_switches_timestamp = 0.0 + + @publish(["td_switches_timestamp", "td_switches_status"]) + def _handler_switches_sync(self, message, timestamp=0.0): + """ + Handles published TD switches data messages. Switches data are captured for reference. + + @param message: published switches data message + @return: none + """ + front_door = struct.unpack('i', bytearray( + message['message'][MsgFieldPositions.START_POS_FIELD_1:MsgFieldPositions.END_POS_FIELD_1]))[0] + pump_track = struct.unpack('i', bytearray( + message['message'][MsgFieldPositions.START_POS_FIELD_2:MsgFieldPositions.END_POS_FIELD_2]))[0] + + self.td_switches_status[TDSwitchesNames.FRONT_DOOR.name] = TDSwitchStatus(front_door).value + self.td_switches_status[TDSwitchesNames.PUMP_TRACK_SWITCH.name] = TDSwitchStatus(pump_track).value + + def cmd_switch_status_override(self, switch: int, status: int, reset: int = NO_RESET) -> int: + """ + Constructs and sends the TD switch status override command + Constraints: + Must be logged into TD. + + @param switch: (int) switch ID that is status is overridden + @param status: (int) status that the switch will be overridden to + @param reset: (int) 1 to reset a previous override, 0 to override + @return: 1 if successful, zero otherwise + """ + reset_value = integer_to_bytearray(reset) + sw = integer_to_bytearray(switch) + st = integer_to_bytearray(status) + payload = reset_value + st + sw + + message = DenaliMessage.build_message(channel_id=DenaliChannels.dialin_to_td_ch_id, + message_id=MsgIds.MSG_ID_TD_SWITCH_STATE_OVERRIDE_REQUEST.value, + payload=payload) + + self.logger.debug("Override switch status") + + # Send message + received_message = self.can_interface.send(message) + + # If there is no content... + if received_message is not None: + + self.logger.debug("Switch " + str(TDSwitchesNames(switch).name) + " to: " + + str(received_message['message'][DenaliMessage.PAYLOAD_START_INDEX])) + # response payload is OK or not OK + return received_message['message'][DenaliMessage.PAYLOAD_START_INDEX] + else: + self.logger.debug("Timeout!!!!") + return False + + def cmd_switches_data_broadcast_interval_override(self, ms: int, reset: int = NO_RESET) -> int: + """ + Constructs and sends the TD switch data publication override command. + Constraints: + Must be logged into TD. + Given interval must be non-zero and a multiple of the TD general task interval (50 ms). + + @param ms: (int) interval (in ms) to override with + @param reset: (int) 1 to reset a previous override, 0 to override + @return: 1 if successful, zero otherwise + """ + + if not check_broadcast_interval_override_ms(ms): + return False + + rst = integer_to_bytearray(reset) + mis = integer_to_bytearray(ms) + payload = rst + mis + + message = DenaliMessage.build_message(channel_id=DenaliChannels.dialin_to_td_ch_id, + message_id=MsgIds.MSG_ID_TD_SWITCHES_PUBLISH_INTERVAL_OVERRIDE_REQUEST.value, + payload=payload) + + self.logger.debug("Override TD switches data broadcast interval") + + # Send message + received_message = self.can_interface.send(message) + + # If there is content... + if received_message is not None: + # self.logger.debug(received_message) + if reset == RESET: + str_res = "reset back to normal" + else: + str_res = str(mis) + self.logger.debug( + "TD Switches data broadcast interval overridden to " + str_res + " ms: " + + str(received_message['message'][DenaliMessage.PAYLOAD_START_INDEX])) + # response payload is OK or not OK + return received_message['message'][DenaliMessage.PAYLOAD_START_INDEX] + else: + self.logger.debug("Timeout!!!!") + return False Index: leahi-dialin/td/treatment_delivery.py =================================================================== diff -u -r12b82879196778f6c83d08751652177ea0fcbf5e -r873e727b657db3d14625ca0de499bfd630cbe502 --- leahi-dialin/td/treatment_delivery.py (.../treatment_delivery.py) (revision 12b82879196778f6c83d08751652177ea0fcbf5e) +++ leahi-dialin/td/treatment_delivery.py (.../treatment_delivery.py) (revision 873e727b657db3d14625ca0de499bfd630cbe502) @@ -5,7 +5,7 @@ # THIS CODE MAY NOT BE COPIED OR REPRODUCED IN ANY FORM, IN PART OR IN # WHOLE, WITHOUT THE EXPLICIT PERMISSION OF THE COPYRIGHT OWNER. # -# @file hemodialysis_device.py +# @file treatment_delivery.py # # @author (last) Dara Navaei # @date (last) 26-Feb-2024 @@ -18,11 +18,13 @@ from .air_pump import TDAirPump from .air_trap import TDAirTrap from .alarms import TDAlarms +from .blood_flow import TDBloodFlow from .bubble_detector import TDBubbleDectector from .buttons import TDButtons from .pressure_sensors import TDPressureSensors from .switches import TDSwitches - +from .valves import TD2WayValves, TDPinchValves +from .voltages import TDVoltages from ..common.msg_defs import MsgIds, MsgFieldPositions from ..protocols.CAN import DenaliMessage, DenaliCanMessenger, DenaliChannels from ..utils.base import AbstractSubSystem, publish, LogManager @@ -107,10 +109,18 @@ self.td_last_debug_event = '' # Create command groups + self.air_pump = TDAirPump(self.can_interface, self.logger) + self.air_trap = TDAirTrap(self.can_interface, self.logger) + self.alarms = TDAlarms(self.can_interface, self.logger) + self.blood_flow = TDBloodFlow(self.can_interface, self.logger) self.bubbles = TDBubbleDectector(self.can_interface, self.logger) self.buttons = TDButtons(self.can_interface, self.logger) + self.pinch_valves = TDPinchValves(self.can_interface, self.logger) self.pressure_sensors = TDPressureSensors(self.can_interface, self.logger) + self.switches = TDSwitches(self.can_interface, self.logger) + self.two_way_valve = TD2WayValves(self.can_interface, self.logger) + self.voltages = TDVoltages(self.can_interface, self.logger) @publish(["td_debug_events_timestamp","td_debug_events"]) def _handler_td_debug_event_sync(self, message, timestamp = 0.0): Index: leahi-dialin/td/valves.py =================================================================== diff -u --- leahi-dialin/td/valves.py (revision 0) +++ leahi-dialin/td/valves.py (revision 873e727b657db3d14625ca0de499bfd630cbe502) @@ -0,0 +1 @@ \ No newline at end of file Index: leahi-dialin/td/voltages.py =================================================================== diff -u --- leahi-dialin/td/voltages.py (revision 0) +++ leahi-dialin/td/voltages.py (revision 873e727b657db3d14625ca0de499bfd630cbe502) @@ -0,0 +1,194 @@ +########################################################################### +# +# Copyright (c) 2021-2024 Diality Inc. - All Rights Reserved. +# +# THIS CODE MAY NOT BE COPIED OR REPRODUCED IN ANY FORM, IN PART OR IN +# WHOLE, WITHOUT THE EXPLICIT PERMISSION OF THE COPYRIGHT OWNER. +# +# @file voltages.py +# +# @author (last) Micahel Garthwaite +# @date (last) 03-Mar-2023 +# @author (original) Sean Nash +# @date (original) 15-Apr-2021 +# +############################################################################ +import struct +from enum import unique +from logging import Logger + +from .constants import RESET, NO_RESET +from ..protocols.CAN import DenaliMessage, DenaliChannels +from ..utils.base import AbstractSubSystem, publish, DialinEnum +from ..utils.conversions import integer_to_bytearray, float_to_bytearray +from ..utils.checks import check_broadcast_interval_override_ms +from ..common.msg_defs import MsgIds, MsgFieldPositions + + +# Monitored voltages +@unique +class TDMonitoredVoltages(DialinEnum): + MONITORED_LINE_1_2V = 0 # Processor voltage (1.2V) + MONITORED_LINE_3_3V = 1 # Logic voltage (3.3V) + MONITORED_LINE_5V_LOGIC = 2 # Logic voltage (5V) + MONITORED_LINE_5V_SENSORS = 3 # Sensors voltage (5V) + MONITORED_LINE_24V = 4 # Actuators voltage (24V) + MONITORED_LINE_24V_REGEN = 5 # Actuators regen voltage (24V) + MONITORED_LINE_FPGA_REF_V = 6 # FPGA ADC reference voltage (1V) + MONITORED_LINE_PBA_REF_V = 7 # PBA ADC reference voltage (3V) + MONITORED_LINE_FPGA_VCC_V = 8 # FPGA Vcc (3V) + MONITORED_LINE_FPGA_AUX_V = 9 # FPGA Vaux (3V) + MONITORED_LINE_FPGA_PVN_V = 10 # FPGA Vpvn (1V) + NUM_OF_MONITORED_VOLTAGE_LINES = 11 # Number of TD operation modes + +class TDVoltages(AbstractSubSystem): + """ + Treatment Delivery (TD) Dialin API sub-class for voltage monitor related commands and data. + """ + + def __init__(self, can_interface, logger: Logger): + """ + TDVoltages constructor + + """ + super().__init__() + self.can_interface = can_interface + self.logger = logger + + if self.can_interface is not None: + channel_id = DenaliChannels.TD_sync_broadcast_ch_id + msg_id = MsgIds.MSG_ID_TD_VOLTAGES_DATA.value + self.can_interface.register_receiving_publication_function(channel_id, msg_id, + self._handler_monitored_voltages_sync) + self.monitored_voltages = [0.0] * TDMonitoredVoltages.NUM_OF_MONITORED_VOLTAGE_LINES.value + self.TD_voltages_timestamp = 0.0 + + + @publish(["td_voltages_timestamp","monitored_voltages"]) + def _handler_monitored_voltages_sync(self, message, timestamp=0.0): + """ + Handles published TD monitored voltages data messages. Voltage data are captured + for reference. + + @param message: published monitored voltages data message + @return: none + """ + + v12 = struct.unpack('f', bytearray( + message['message'][MsgFieldPositions.START_POS_FIELD_1:MsgFieldPositions.END_POS_FIELD_1])) + v33 = struct.unpack('f', bytearray( + message['message'][MsgFieldPositions.START_POS_FIELD_2:MsgFieldPositions.END_POS_FIELD_2])) + v5l = struct.unpack('f', bytearray( + message['message'][MsgFieldPositions.START_POS_FIELD_3:MsgFieldPositions.END_POS_FIELD_3])) + v5s = struct.unpack('f', bytearray( + message['message'][MsgFieldPositions.START_POS_FIELD_4:MsgFieldPositions.END_POS_FIELD_4])) + v24 = struct.unpack('f', bytearray( + message['message'][MsgFieldPositions.START_POS_FIELD_5:MsgFieldPositions.END_POS_FIELD_5])) + v24g = struct.unpack('f', bytearray( + message['message'][MsgFieldPositions.START_POS_FIELD_6:MsgFieldPositions.END_POS_FIELD_6])) + vfr = struct.unpack('f', bytearray( + message['message'][MsgFieldPositions.START_POS_FIELD_7:MsgFieldPositions.END_POS_FIELD_7])) + vpr = struct.unpack('f', bytearray( + message['message'][MsgFieldPositions.START_POS_FIELD_8:MsgFieldPositions.END_POS_FIELD_8])) + vfc = struct.unpack('f', bytearray( + message['message'][MsgFieldPositions.START_POS_FIELD_9:MsgFieldPositions.END_POS_FIELD_9])) + vfa = struct.unpack('f', bytearray( + message['message'][MsgFieldPositions.START_POS_FIELD_10:MsgFieldPositions.END_POS_FIELD_10])) + vfp = struct.unpack('f', bytearray( + message['message'][MsgFieldPositions.START_POS_FIELD_11:MsgFieldPositions.END_POS_FIELD_11])) + + self.monitored_voltages[TDMonitoredVoltages.MONITORED_LINE_1_2V.value] = v12[0] + self.monitored_voltages[TDMonitoredVoltages.MONITORED_LINE_3_3V.value] = v33[0] + self.monitored_voltages[TDMonitoredVoltages.MONITORED_LINE_5V_LOGIC.value] = v5l[0] + self.monitored_voltages[TDMonitoredVoltages.MONITORED_LINE_5V_SENSORS.value] = v5s[0] + self.monitored_voltages[TDMonitoredVoltages.MONITORED_LINE_24V.value] = v24[0] + self.monitored_voltages[TDMonitoredVoltages.MONITORED_LINE_24V_REGEN.value] = v24g[0] + self.monitored_voltages[TDMonitoredVoltages.MONITORED_LINE_FPGA_REF_V.value] = vfr[0] + self.monitored_voltages[TDMonitoredVoltages.MONITORED_LINE_PBA_REF_V.value] = vpr[0] + self.monitored_voltages[TDMonitoredVoltages.MONITORED_LINE_FPGA_VCC_V.value] = vfc[0] + self.monitored_voltages[TDMonitoredVoltages.MONITORED_LINE_FPGA_AUX_V.value] = vfa[0] + self.monitored_voltages[TDMonitoredVoltages.MONITORED_LINE_FPGA_PVN_V.value] = vfp[0] + self.td_voltages_timestamp = timestamp + + def cmd_monitored_voltage_override(self, signal: int = 0, volts: float = 0.0, reset: int = NO_RESET) -> int: + """ + Constructs and sends the TD monitored voltage override command + Constraints: + Must be logged into TD. + Given signal must be valid member of TDMonitoredVoltages enum + + @param signal: integer - ID of signal to override + @param volts: float - value (in volts) to override signal with + @param reset: integer - 1 to reset a previous override, 0 to override + @return: 1 if successful, zero otherwise + """ + + rst = integer_to_bytearray(reset) + vlt = float_to_bytearray(volts) + idx = integer_to_bytearray(signal) + payload = rst + vlt + idx + + message = DenaliMessage.build_message(channel_id=DenaliChannels.dialin_to_td_ch_id, + message_id=MsgIds.MSG_ID_TD_VOLTAGE_OVERRIDE_REQUEST.value, + payload=payload) + + self.logger.debug("override monitored TD voltage for signal " + str(signal)) + + # Send message + received_message = self.can_interface.send(message) + + # If there is content... + if received_message is not None: + if reset == RESET: + str_res = "reset back to normal. " + else: + str_res = str(volts) + " V. " + self.logger.debug("Monitored TD voltage overridden to " + str_res + + str(received_message['message'][DenaliMessage.PAYLOAD_START_INDEX])) + # response payload is OK or not OK + return received_message['message'][DenaliMessage.PAYLOAD_START_INDEX] + else: + self.logger.debug("Timeout!!!!") + return False + + def cmd_monitored_voltages_broadcast_interval_override(self, ms: int = 1000, reset: int = NO_RESET) -> int: + """ + Constructs and sends the monitored TD voltages broadcast interval override command + Constraints: + Must be logged into TD. + Given interval must be non-zero and a multiple of the TD general task interval (50 ms). + + @param ms: integer - interval (in ms) to override with + @param reset: integer - 1 to reset a previous override, 0 to override + @return: 1 if successful, zero otherwise + """ + + if not check_broadcast_interval_override_ms(ms): + return False + + rst = integer_to_bytearray(reset) + mis = integer_to_bytearray(ms) + payload = rst + mis + + message = DenaliMessage.build_message(channel_id=DenaliChannels.dialin_to_td_ch_id, + message_id=MsgIds.MSG_ID_TD_VOLTAGE_PUBLISH_INTERVAL_OVERRIDE_REQUEST.value, + payload=payload) + + self.logger.debug("override monitored TD voltages broadcast interval") + + # Send message + received_message = self.can_interface.send(message) + + # If there is content... + if received_message is not None: + if reset == RESET: + str_res = "reset back to normal: " + else: + str_res = str(ms) + " ms: " + self.logger.debug("TD monitored voltages broadcast interval overridden to " + str_res + + str(received_message['message'][DenaliMessage.PAYLOAD_START_INDEX])) + # response payload is OK or not OK + return received_message['message'][DenaliMessage.PAYLOAD_START_INDEX] + else: + self.logger.debug("Timeout!!!!") + return False