Index: leahi-dialin/common/msg_ids.py =================================================================== diff -u -r23331c8d78ffe7bf583fa351e1bea065e19d656c -r4e2e867e7ee1fcf889b286167a3d6bb7f4fefb80 --- leahi-dialin/common/msg_ids.py (.../msg_ids.py) (revision 23331c8d78ffe7bf583fa351e1bea065e19d656c) +++ leahi-dialin/common/msg_ids.py (.../msg_ids.py) (revision 4e2e867e7ee1fcf889b286167a3d6bb7f4fefb80) @@ -108,44 +108,6 @@ MSG_ID_TD_SAFETY_SHUTDOWN_OVERRIDE_REQUEST = 0x8022 MSG_ID_TD_PINCH_VALVE_SET_POSITION_REQUEST = 0x8023 MSG_ID_TD_PINCH_VALVE_HOME_REQUEST = 0x8024 - - MSG_ID_TESTER_LOGIN_REQUEST = 0x8000 - MSG_ID_TD_SOFTWARE_RESET_REQUEST = 0x8001 - MSG_ID_TD_SEND_TEST_CONFIGURATION = 0x8002 - MSG_ID_TD_BUBBLE_OVERRIDE_REQUEST = 0x8003 - MSG_ID_TD_VOLTAGE_PUBLISH_INTERVAL_OVERRIDE_REQUEST = 0x8004 - MSG_ID_TD_VOLTAGE_OVERRIDE_REQUEST = 0x8005 - MSG_ID_TD_BUBBLE_PUBLISH_INTERVAL_OVERRIDE_REQUEST = 0x8006 - MSG_ID_TD_PRESSURE_OVERRIDE_REQUEST = 0x8007 - MSG_ID_TD_AIR_PUMP_SET_STATE_REQUEST = 0x8008 - MSG_ID_TD_AIR_PUMP_PUBLISH_INTERVAL_OVERRIDE_REQUEST = 0x8009 - MSG_ID_TD_SWITCHES_PUBLISH_INTERVAL_OVERRIDE_REQUEST = 0x800A - MSG_ID_TD_SWITCH_STATE_OVERRIDE_REQUEST = 0x800B - MSG_ID_TD_OFF_BUTTON_OVERRIDE_REQUEST = 0x800C - MSG_ID_TD_STOP_BUTTON_OVERRIDE_REQUEST = 0x800D - MSG_ID_TD_ALARM_LAMP_PATTERN_OVERRIDE_REQUEST = 0x800E - MSG_ID_TD_ALARM_AUDIO_LEVEL_OVERRIDE_REQUEST = 0x800F - MSG_ID_TD_ALARM_AUDIO_CURRENT_HG_OVERRIDE_REQUEST = 0x8010 - MSG_ID_TD_ALARM_AUDIO_CURRENT_LG_OVERRIDE_REQUEST = 0x8011 - MSG_ID_TD_BACKUP_ALARM_AUDIO_CURRENT_OVERRIDE_REQUEST = 0x8012 - MSG_ID_TD_PRESSURE_PUBLISH_INTERVAL_OVERRIDE_REQUEST = 0x8013 - MSG_ID_TD_AIR_TRAP_LEVEL_OVERRIDE_REQUEST = 0x8014 - MSG_ID_TD_AIR_TRAP_LEVEL_RAW_OVERRIDE_REQUEST = 0x8015 - MSG_ID_TD_AIR_TRAP_PUBLISH_INTERVAL_OVERRIDE_REQUEST = 0x8016 - MSG_ID_TD_2_WAY_VALVE_SET_STATE_REQUEST = 0x8017 - MSG_ID_TD_ROTARY_PINCH_VALVE_SET_POS_REQUEST = 0x8018 - MSG_ID_TD_ROTARY_PINCH_VALVE_STATUS_OVERRIDE_REQUEST = 0x8019 - MSG_ID_TD_ROTARY_PINCH_VALVE_POSITION_OVERRIDE_REQUEST = 0x801A - MSG_ID_TD_VALVES_PUBLISH_INTERVAL_OVERRIDE_REQUEST = 0x801B - MSG_ID_TD_ALARM_STATUS_PUBLISH_INTERVAL_OVERRIDE_REQUEST = 0x801C - MSG_ID_TD_ALARM_INFO_PUBLISH_INTERVAL_OVERRIDE_REQUEST = 0x801D - MSG_ID_TD_ALARM_START_TIME_OVERRIDE_REQUEST = 0x801E - MSG_ID_TD_ALARM_CLEAR_ALL_ALARMS_REQUEST = 0x801F - MSG_ID_TD_WATCHDOG_OVERRIDE_REQUEST = 0x8020 - MSG_ID_TD_ALARM_STATE_OVERRIDE_REQUEST = 0x8021 - MSG_ID_TD_SAFETY_SHUTDOWN_OVERRIDE_REQUEST = 0x8022 - MSG_ID_TD_PINCH_VALVE_SET_POSITION_REQUEST = 0x8023 - MSG_ID_TD_PINCH_VALVE_HOME_REQUEST = 0x8024 MSG_ID_TD_BLOOD_PUMP_PUBLISH_INTERVAL_OVERRIDE_REQUEST = 0x8025 MSG_ID_TD_BLOOD_PUMP_SET_FLOW_RATE_REQUEST = 0x8026 MSG_ID_TD_BLOOD_PUMP_SET_SPEED_REQUEST = 0x8027 Fisheye: Tag 4e2e867e7ee1fcf889b286167a3d6bb7f4fefb80 refers to a dead (removed) revision in file `leahi-dialin/td/air_pump.py'. Fisheye: No comparison available. Pass `N' to diff? Fisheye: Tag 4e2e867e7ee1fcf889b286167a3d6bb7f4fefb80 refers to a dead (removed) revision in file `leahi-dialin/td/air_trap.py'. Fisheye: No comparison available. Pass `N' to diff? Fisheye: Tag 4e2e867e7ee1fcf889b286167a3d6bb7f4fefb80 refers to a dead (removed) revision in file `leahi-dialin/td/alarms.py'. Fisheye: No comparison available. Pass `N' to diff? Fisheye: Tag 4e2e867e7ee1fcf889b286167a3d6bb7f4fefb80 refers to a dead (removed) revision in file `leahi-dialin/td/blood_flow.py'. Fisheye: No comparison available. Pass `N' to diff? Fisheye: Tag 4e2e867e7ee1fcf889b286167a3d6bb7f4fefb80 refers to a dead (removed) revision in file `leahi-dialin/td/bubble_detector.py'. Fisheye: No comparison available. Pass `N' to diff? Fisheye: Tag 4e2e867e7ee1fcf889b286167a3d6bb7f4fefb80 refers to a dead (removed) revision in file `leahi-dialin/td/buttons.py'. Fisheye: No comparison available. Pass `N' to diff? Fisheye: Tag 4e2e867e7ee1fcf889b286167a3d6bb7f4fefb80 refers to a dead (removed) revision in file `leahi-dialin/td/dd_proxy.py'. Fisheye: No comparison available. Pass `N' to diff? Index: leahi-dialin/td/modules/__init__.py =================================================================== diff -u --- leahi-dialin/td/modules/__init__.py (revision 0) +++ leahi-dialin/td/modules/__init__.py (revision 4e2e867e7ee1fcf889b286167a3d6bb7f4fefb80) @@ -0,0 +1 @@ \ No newline at end of file Index: leahi-dialin/td/modules/air_pump.py =================================================================== diff -u --- leahi-dialin/td/modules/air_pump.py (revision 0) +++ leahi-dialin/td/modules/air_pump.py (revision 4e2e867e7ee1fcf889b286167a3d6bb7f4fefb80) @@ -0,0 +1,140 @@ +########################################################################### +# +# Copyright (c) 2022-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_pump.py +# +# @author (last) Micahel Garthwaite +# @date (last) 10-Mar-2023 +# @author (original) Micahel Garthwaite +# @date (original) 01-Nov-2024 +# +############################################################################ + +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 + + +class TDAirPump(AbstractSubSystem): + """ + TDAirPump + + Treatment Delivery (TD) Dialin API sub-class for air pump related commands. + """ + + 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_PUMP_DATA.value + self.can_interface.register_receiving_publication_function(channel_id, msg_id, + self._handler_air_pump_sync) + + self.air_pump_state = 0 + self.td_air_pump_timestamp = 0.0 + + @publish(["td_air_pump_timestamp", "air_pump_state"]) + def _handler_air_pump_sync(self, message, timestamp=0.0): + """ + Handles published air pump data messages. + + @param message: published air pump data message as: air pump state + @return: None + """ + aps = struct.unpack('i', bytearray( + message['message'][MsgFieldPositions.START_POS_FIELD_1:MsgFieldPositions.END_POS_FIELD_1])) + + self.air_pump_state = aps[0] + self.td_air_pump_timestamp = timestamp + + def cmd_air_pump_set_state(self, state: int) -> int: + """ + Constructs and sends the air pump set state command. + AIR_PUMP_STATE_INIT = 0, ///< Air Pump Initialize state + AIR_PUMP_STATE_OFF, ///< Air Pump Off state + AIR_PUMP_STATE_ON, ///< Air Pump On state + NUM_OF_AIR_PUMP_STATES, ///< Number of air pump states + + Constraints: + Must be logged into TD. + + """ + + idx = integer_to_bytearray(state) + payload = idx + + message = DenaliMessage.build_message(channel_id=DenaliChannels.dialin_to_td_ch_id, + message_id=MsgIds.MSG_ID_TD_AIR_PUMP_SET_STATE_REQUEST.value, + payload=payload) + + self.logger.debug("setting air pump state to" + str(state)) + + # 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_pump_data_broadcast_interval_override(self, ms: int, reset: int = NO_RESET) -> int: + """ + Constructs and sends the air pump 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_PUMP_PUBLISH_INTERVAL_OVERRIDE_REQUEST.value, + payload=payload) + + self.logger.debug("override TD air pump 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 pump 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/modules/air_trap.py =================================================================== diff -u --- leahi-dialin/td/modules/air_trap.py (revision 0) +++ leahi-dialin/td/modules/air_trap.py (revision 4e2e867e7ee1fcf889b286167a3d6bb7f4fefb80) @@ -0,0 +1,242 @@ +########################################################################### +# +# 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 AirTrapValves(DialinEnum): + VBT = 0 + +@unique +class AirTrapState(DialinEnum): + STATE_CLOSED = 0 + STATE_OPEN = 1 + +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 + + def cmd_set_hd_air_trap_valve(self, valve: int = AirTrapValves.VBT.value, + valve_state: int = AirTrapState.STATE_CLOSED.value) -> int: + """ + Constructs and sends an open/close command to the TD air trap valve + + @param valve: air trap valve ID ( VBT = 0 ) + @param valve_state: air trap valve state (open or close) + @returns 1 if successful, zero otherwise + """ + + vlv = integer_to_bytearray(valve) + sts = integer_to_bytearray(valve_state) + payload = sts + vlv + + message = DenaliMessage.build_message(channel_id=DenaliChannels.dialin_to_hd_ch_id, + message_id=MsgIds.MSG_ID_TD_2_WAY_VALVE_SET_STATE_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("Opening air trap valve") + # response payload is OK or not OK + return received_message['message'][DenaliMessage.PAYLOAD_START_INDEX] + else: + self.logger.debug("Opening air trap valve timeout!!") + return False Index: leahi-dialin/td/modules/alarms.py =================================================================== diff -u --- leahi-dialin/td/modules/alarms.py (revision 0) +++ leahi-dialin/td/modules/alarms.py (revision 4e2e867e7ee1fcf889b286167a3d6bb7f4fefb80) @@ -0,0 +1,693 @@ +########################################################################### +# +# 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 alarms.py +# +# @author (last) Michael Garthwaite +# @date (last) 04-Oct-2023 +# @author (original) Peter Lucia +# @date (original) 02-Apr-2020 +# +############################################################################ +import struct +from logging import Logger +from enum import unique +from ..utils.base import DialinEnum + +from .constants import RESET, NO_RESET +from ..common.msg_defs import MsgIds, MsgFieldPositions +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 TDAlarms(AbstractSubSystem): + """ + TD interface containing alarm related commands. + """ + + # Alarm lamp patterns + 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 + 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): + 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 + END_POS_ALARM_STATE = START_POS_ALARM_STATE + 4 + START_POS_ALARM_TOP = END_POS_ALARM_STATE + END_POS_ALARM_TOP = START_POS_ALARM_TOP + 4 + START_POS_ALARM_ESCALATES_IN = END_POS_ALARM_TOP + END_POS_ALARM_ESCALATES_IN = START_POS_ALARM_ESCALATES_IN + 4 + START_POS_ALARM_SILENCE_EXPIRES_IN = END_POS_ALARM_ESCALATES_IN + END_POS_ALARM_SILENCE_EXPIRES_IN = START_POS_ALARM_SILENCE_EXPIRES_IN + 4 + START_POS_ALARMS_FLAGS = END_POS_ALARM_SILENCE_EXPIRES_IN + END_POS_ALARMS_FLAGS = START_POS_ALARMS_FLAGS + 2 + + 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_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.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) + msg_id = MsgIds.MSG_ID_ALARM_CLEARED.value + self.can_interface.register_receiving_publication_function(channel_id, msg_id, + self._handler_alarm_clear) + 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.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.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 TD alarm activation and alarm clear messages + self.alarm_states = [False] * 500 + # alarm condition states based on received TD alarm activation and clear condition messages + self.alarm_conditions = [False] * 500 + # alarm priorities based on received TD alarm activation messages + self.alarm_priorities = [0] * 500 + # alarm ranks based on received TD alarm activation messages + self.alarm_ranks = [0] * 500 + # 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 + + # alarm information + self.alarm_data_type = dict() + self.alarm_volume = 0 + self.alarm_audio_curr_hg = 0.0 + self.alarm_audio_curr_lg = 0.0 + 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_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 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 + # the corresponding format in the python struct + if 'U32' in event_data_type or 'BOOL' in event_data_type or 'NONE' in event_data_type: + struct_unpack_type = 'I' + elif 'S32' in event_data_type: + struct_unpack_type = 'i' + elif 'F32' in event_data_type: + struct_unpack_type = 'f' + + self.alarm_data_type[event_data_type] = struct_unpack_type + + def clear_dialin_alarms(self): + """ + Clears the alarms states in Dialin. + + @return: none + """ + for x in range(500): + self.alarm_states[x] = False + + @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 + for reference. + + @param message: published alarm status data message + @return: none + """ + self.alarms_priority_state = struct.unpack('i', bytearray(message['message'][MsgFieldPositions.START_POS_FIELD_1:MsgFieldPositions.END_POS_FIELD_1]))[0] + self.alarm_top = struct.unpack('i', bytearray(message['message'][MsgFieldPositions.START_POS_FIELD_2:MsgFieldPositions.END_POS_FIELD_2]))[0] + self.alarms_escalates_in = struct.unpack('i', bytearray(message['message'][MsgFieldPositions.START_POS_FIELD_3:MsgFieldPositions.END_POS_FIELD_3]))[0] + 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.td_alarm_status_timestamp = timestamp + + @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 TD alarm activation messages. + + @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[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[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])) + + priority = struct.unpack('i', bytearray(message['message'][MsgFieldPositions.START_POS_FIELD_6:MsgFieldPositions.END_POS_FIELD_6])) + rank = struct.unpack('i', bytearray(message['message'][MsgFieldPositions.START_POS_FIELD_7:MsgFieldPositions.END_POS_FIELD_7])) + clr_top_only = struct.unpack('i', bytearray(message['message'][MsgFieldPositions.START_POS_FIELD_8:MsgFieldPositions.END_POS_FIELD_8])) + + self.logger.debug("Alarm ID: %d %d %d" % (alarm_id[0], data_1[0], data_2[0])) + self.alarm_states[alarm_id[0]] = True + self.alarm_conditions[alarm_id[0]] = True + self.alarm_priorities[alarm_id[0]] = priority[0] + 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.td_alarm_triggered_timestamp = timestamp + + @publish(["TD_alarm_cleared_timestamp", "alarm_states", "alarm_conditions"]) + def _handler_alarm_clear(self, message, timestamp=0.0): + """ + Handles published TD alarm clear messages. + + @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.td_alarm_cleared_timestamp = timestamp + + @publish(["td_alarm_clr_condition_timestamp", "alarm_conditions", "alarm_conditions"]) + def _handler_alarm_condition_clear(self, message, timestamp=0.0): + """ + Handles published TD alarm clear alarm condition messages. + + @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.td_alarm_clr_condition_timestamp = timestamp + + @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 TD alarm information broadcast messages. + + @param message: published TD alarm information message + @return: none + """ + + vol = struct.unpack('i', bytearray( + message['message'][MsgFieldPositions.START_POS_FIELD_1:MsgFieldPositions.END_POS_FIELD_1])) + ach = struct.unpack('f', bytearray( + message['message'][MsgFieldPositions.START_POS_FIELD_2:MsgFieldPositions.END_POS_FIELD_2])) + acl = struct.unpack('f', bytearray( + message['message'][MsgFieldPositions.START_POS_FIELD_3:MsgFieldPositions.END_POS_FIELD_3])) + bac = struct.unpack('f', bytearray( + message['message'][MsgFieldPositions.START_POS_FIELD_4:MsgFieldPositions.END_POS_FIELD_4])) + saf = struct.unpack('i', bytearray( + message['message'][MsgFieldPositions.START_POS_FIELD_5:MsgFieldPositions.END_POS_FIELD_5])) + acp = struct.unpack('i', bytearray( + message['message'][MsgFieldPositions.START_POS_FIELD_6:MsgFieldPositions.END_POS_FIELD_6])) + trs = struct.unpack('B', bytearray( + message['message'][MsgFieldPositions.START_POS_FIELD_7:MsgFieldPositions.START_POS_FIELD_7+1])) + trb = struct.unpack('B', bytearray( + message['message'][MsgFieldPositions.START_POS_FIELD_7+1:MsgFieldPositions.START_POS_FIELD_7+2])) + tet = struct.unpack('B', bytearray( + message['message'][MsgFieldPositions.START_POS_FIELD_7+2:MsgFieldPositions.START_POS_FIELD_7+3])) + srs = struct.unpack('B', bytearray( + message['message'][MsgFieldPositions.START_POS_FIELD_7+3:MsgFieldPositions.START_POS_FIELD_7+4])) + srb = struct.unpack('B', bytearray( + message['message'][MsgFieldPositions.START_POS_FIELD_7+4:MsgFieldPositions.START_POS_FIELD_7+5])) + set = struct.unpack('B', bytearray( + message['message'][MsgFieldPositions.START_POS_FIELD_7+5:MsgFieldPositions.START_POS_FIELD_7+6])) + + self.alarm_volume = vol[0] + self.alarm_audio_curr_hg = ach[0] + self.alarm_audio_curr_lg = acl[0] + 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.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_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 TD. + Given alarm must be valid. + If inactivating alarm, given alarm must be recoverable (clearable). + + @param alarm: integer - ID of alarm to override + @param state: integer - 1 for alarm active, 0 for alarm inactive + @param reset: integer - 1 to reset a previous override, 0 to override + @return: 1 if successful, zero otherwise + """ + + rst = integer_to_bytearray(reset) + sta = integer_to_bytearray(state) + alm = integer_to_bytearray(alarm) + payload = rst + sta + alm + + 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 + 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 = ("active" if state != 0 else "inactive") + self.logger.debug("Alarm " + str(alarm) + " " + 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_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 TD. + Given alarm must be valid. + + @param alarm: integer - ID of alarm to override + @param time_ms: integer - time (in ms) since alarm was activated + @param reset: integer - 1 to reset a previous override, 0 to override + @return: 1 if successful, zero otherwise + """ + + rst = integer_to_bytearray(reset) + ms = integer_to_bytearray(time_ms) + alm = integer_to_bytearray(alarm) + payload = rst + ms + alm + + 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") + + # 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(time_ms) + self.logger.debug("Alarm time since activated 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 + + def cmd_alarm_lamp_pattern_override(self, pattern: int, reset: int = NO_RESET) -> int: + """ + Constructs and sends the alarm lamp pattern override command. + Constraints: + 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 + @param reset: integer - 1 to reset a previous override, 0 to override + @return: 1 if successful, zero otherwise + + Patterns: \n + 0 = off \n + 1 = ok \n + 2 = fault \n + 3 = high \n + 4 = medium \n + 5 = low \n + 6 = manual + """ + rst = integer_to_bytearray(reset) + pat = integer_to_bytearray(pattern) + payload = rst + pat + + 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") + + # 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_pat = "reset back to normal" + elif pattern == self.TD_ALARM_LAMP_PATTERN_OFF: + str_pat = "off" + elif pattern == self.TD_ALARM_LAMP_PATTERN_OK: + str_pat = "ok" + elif pattern == self.TD_ALARM_LAMP_PATTERN_FAULT: + str_pat = "fault" + elif pattern == self.TD_ALARM_LAMP_PATTERN_HIGH: + str_pat = "high" + elif pattern == self.TD_ALARM_LAMP_PATTERN_MEDIUM: + str_pat = "medium" + elif pattern == self.TD_ALARM_LAMP_PATTERN_LOW: + str_pat = "low" + else: + str_pat = "manual" + self.logger.debug("Alarm lamp pattern overridden to " + str_pat + ":" + + 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_alarm_info_broadcast_interval_override(self, ms: int = 1000, reset: int = NO_RESET): + """ + Constructs and sends the alarm information 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_ALARM_INFO_PUBLISH_INTERVAL_OVERRIDE_REQUEST.value, + payload=payload) + + self.logger.debug("override alarm information 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("Alarm information 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_alarm_status_broadcast_interval_override(self, ms: int = 250, reset: int = NO_RESET): + """ + Constructs and sends the alarm status 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_ALARM_STATUS_PUBLISH_INTERVAL_OVERRIDE_REQUEST.value, + payload=payload) + + self.logger.debug("override alarm status 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("Alarm status 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_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 TD. + Given volume must be an integer between 1 and 5. + + @param volume: integer - alarm audio volume level (1..5) + @param reset: integer - 1 to reset a previous override, 0 to override + @return: 1 if successful, zero otherwise + """ + + rst = integer_to_bytearray(reset) + vol = integer_to_bytearray(volume) + payload = rst + vol + + 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") + + # 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(volume) + ": " + self.logger.debug("Alarm audio volume level 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_alarm_audio_current_hg_override(self, current: float = 0.0, reset: int = NO_RESET): + """ + Constructs and sends the alarm audio current (high gain) override command + Constraints: + 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 + @return: 1 if successful, zero otherwise + """ + + rst = integer_to_bytearray(reset) + cur = float_to_bytearray(current) + payload = rst + cur + + 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") + + # 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(current) + " mA: " + self.logger.debug("Alarm audio high gain current 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_alarm_audio_current_lg_override(self, current: float = 0.0, reset: int = NO_RESET): + """ + Constructs and sends the alarm audio current (low gain) override command + Constraints: + 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 + @return: 1 if successful, zero otherwise + """ + + rst = integer_to_bytearray(reset) + cur = float_to_bytearray(current) + payload = rst + cur + + 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") + + # 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(current) + " mA: " + self.logger.debug("Alarm audio low gain current 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_alarm_backup_audio_current_override(self, current: float = 0.0, reset: int = NO_RESET): + """ + Constructs and sends the backup alarm audio current override command + Constraints: + 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 + @return: 1 if successful, zero otherwise + """ + + rst = integer_to_bytearray(reset) + cur = float_to_bytearray(current) + payload = rst + cur + + 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") + + # 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(current) + " mA: " + self.logger.debug("Alarm backup audio current 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/modules/blood_flow.py =================================================================== diff -u --- leahi-dialin/td/modules/blood_flow.py (revision 0) +++ leahi-dialin/td/modules/blood_flow.py (revision 4e2e867e7ee1fcf889b286167a3d6bb7f4fefb80) @@ -0,0 +1,390 @@ +########################################################################### +# +# 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_flow_rate_request(self, flow: int, mode: int = PUMP_CONTROL_MODE_CLOSED_LOOP) -> int: + """ + Constructs and sends the blood flow set point command + Constraints: + Must be logged into TD. + + @param flow: integer - flow set point (in mL/min) (negative for reverse direction) + @param mode: integer - 0 for closed loop control mode or 1 for open loop control mode + @return: 1 if successful, zero otherwise + """ + + flo = integer_to_bytearray(flow) + mod = integer_to_bytearray(mode) + payload = flo + mod + + message = DenaliMessage.build_message(channel_id=DenaliChannels.dialin_to_td_ch_id, + message_id=MsgIds.MSG_ID_TD_BLOOD_PUMP_SET_FLOW_RATE_REQUEST.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_set_speed_rate_request(self, rpm: int) -> int: + """ + Constructs and sends the blood flow set point command + Constraints: + Must be logged into TD. + + @param rpm: integer - speed set point (in rpm) + @return: 1 if successful, zero otherwise + """ + + rpm = integer_to_bytearray(rpm) + payload = rpm + + message = DenaliMessage.build_message(channel_id=DenaliChannels.dialin_to_td_ch_id, + message_id=MsgIds.MSG_ID_TD_BLOOD_PUMP_SET_SPEED_REQUEST.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_TD_BLOOD_PUMP_MEASURED_FLOW_RATE_OVERRIDE_REQUEST.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_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_TD_BLOOD_PUMP_MEASURED_MOTOR_SPEED_OVERRIDE_REQUEST.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_TD_BLOOD_PUMP_MEASURED_ROTOR_SPEED_OVERRIDE_REQUEST.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_TD_BLOOD_PUMP_PUBLISH_INTERVAL_OVERRIDE_REQUEST.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_TD_BLOOD_PUMP_ROTOR_COUNT_OVERRIDE_REQUEST.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/modules/bubble_detector.py =================================================================== diff -u --- leahi-dialin/td/modules/bubble_detector.py (revision 0) +++ leahi-dialin/td/modules/bubble_detector.py (revision 4e2e867e7ee1fcf889b286167a3d6bb7f4fefb80) @@ -0,0 +1,170 @@ +########################################################################### +# +# 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 air_bubbles.py +# +# @author (last) Sean Nash +# @date (last) 04-May-2023 +# @author (original) Peman Montazemi +# @date (original) 18-May-2021 +# +############################################################################ +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.conversions import integer_to_bytearray + + +class TDBubbleDectector(AbstractSubSystem): + """ + TDAirBubbles + + Treatment Delivery (TD) Dialin API sub-class for air bubbles related commands. + ADA: Air bubble Detector Arterial + ADV: Air bubble Detector Venous + """ + + # Air bubble detectors + ADV = 0 # Air bubble Detector Venous + + # Air bubble detectors status + BUBBLE_DETECTED_STATUS = 0 # Air bubble detected + FLUID_DETECTED_STATUS = 1 # Fluid (no air bubble) detected + + # Air bubble detectors state machine states + AIR_BUBBLE_NORMAL_STATE = 0 # Normal state + AIR_BUBBLE_SELF_TEST_STATE = 1 # Self-test state + + 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_BUBBLES_DATA.value + self.can_interface.register_receiving_publication_function(channel_id, msg_id, + self._handler_air_bubbles_data_sync) + + self.td_air_bubbles_timestamp = 0.0 + # Initialize status of ADV air bubble detectors to fluid (no air bubble) detected + self.air_bubbles_status = [self.FLUID_DETECTED_STATUS] + + # Initialize state of ADV air bubble detectors state machine to normal + self.air_bubbles_state = [self.AIR_BUBBLE_NORMAL_STATE] + + @publish(["td_air_bubbles_timestamp", "air_bubbles_status", "air_bubbles_state"]) + def _handler_air_bubbles_data_sync(self, message, timestamp=0.0): + """ + Handles published air bubbles data messages. Air bubble status and state are captured + for ADV detector. + + @param message: published air bubbles data message as: ADV status, ADV state + @return: None + """ + + adv_status = struct.unpack('i', bytearray( + message['message'][MsgFieldPositions.START_POS_FIELD_1:MsgFieldPositions.END_POS_FIELD_1])) + adv_state = struct.unpack('i', bytearray( + message['message'][MsgFieldPositions.START_POS_FIELD_2:MsgFieldPositions.END_POS_FIELD_2])) + + self.air_bubbles_status = [adv_status[0]] + self.air_bubbles_state = [adv_state[0]] + self.td_air_bubbles_timestamp = timestamp + + def cmd_air_bubble_status_override(self, status: int, index: int, reset: int = NO_RESET) -> int: + """ + Constructs and sends the air bubble detector status override command + Constraints: + Must be logged into TD. + Given detector must be one of the detectors listed below. + + @param status: unsigned int - status (0=air bubble, 1=fluid) to override detector with + @param index: integer - 0 for ADV status override + @param reset: integer - 1 to reset a previous override, 0 to override + @return: 1 if successful, zero otherwise + """ + + rst = integer_to_bytearray(reset) + i = integer_to_bytearray(index) + stat = integer_to_bytearray(status) + payload = rst + stat + i + + message = DenaliMessage.build_message(channel_id=DenaliChannels.dialin_to_td_ch_id, + message_id=MsgIds.MSG_ID_TD_BUBBLE_OVERRIDE_REQUEST.value, + payload=payload) + + if index == self.ADV: + self.logger.debug("Override air bubble detector ADV status value") + + # 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_bubbles_data_broadcast_interval_override(self, ms: int, reset: int = NO_RESET) -> int: + """ + Constructs and sends the air bubbles data broadcast interval override command + Constraints: + Must be logged into TD. + Given interval must be positive non-zero and a multiple of the TD priority 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 ms > 0 and ms % 50 == 0: + 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_BUBBLE_PUBLISH_INTERVAL_OVERRIDE_REQUEST.value, + payload=payload) + + self.logger.debug("Override TD air bubbles data broadcast interval to"+str(ms)+"ms") + + # 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 bubbles 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 + elif ms <= 0: + self.logger.debug("ms must be positive non-zero.") + return False + elif ms % 50 != 0: + self.logger.debug("ms must be a multiple of 50.") + return False + else: + self.logger.debug("ms must be an integer.") + return False \ No newline at end of file Index: leahi-dialin/td/modules/buttons.py =================================================================== diff -u --- leahi-dialin/td/modules/buttons.py (revision 0) +++ leahi-dialin/td/modules/buttons.py (revision 4e2e867e7ee1fcf889b286167a3d6bb7f4fefb80) @@ -0,0 +1,157 @@ +########################################################################### +# +# 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 buttons.py +# +# @author (last) Micahel Garthwaite +# @date (last) 05-Nov-2024 +# @author (original) Peter Lucia +# @date (original) 14-Oct-2024 +# +############################################################################ +import struct +from logging import Logger + +from .constants import RESET, NO_RESET +from ..common.msg_defs import MsgIds, MsgFieldPositions +from ..protocols.CAN import DenaliMessage, DenaliCanMessenger, DenaliChannels +from ..utils.base import AbstractSubSystem, publish +from ..utils.conversions import integer_to_bytearray, byte_to_bytearray + + +class TDButtons(AbstractSubSystem): + """ + Treatment Delivery (TD) Dialin API sub-class for button related commands. + """ + + def __init__(self, can_interface: DenaliCanMessenger, logger: Logger): + """ + TD_Buttons constructor + + @param can_interface: the Denali CAN interface object + """ + + super().__init__() + self.can_interface = can_interface + self.logger = logger + self.poweroff_timeout_expired = False + + if self.can_interface is not None: + self.can_interface.register_receiving_publication_function(DenaliChannels.td_to_ui_ch_id, + MsgIds.MSG_ID_OFF_BUTTON_PRESS_REQUEST.value, + self._handler_poweroff_timeout_occurred) + self.td_power_off_timestamp = 0.0 + + def reset_poweroff_timeout_expired(self): + """ + Resets the dialin poweroff timeout flag to False + + @return: None + """ + + self.poweroff_timeout_expired = False + + @publish(['td_power_off_timestamp', "poweroff_timeout_expired"]) + def _handler_poweroff_timeout_occurred(self, message, timestamp=0.0): + """ + Poweroff timeout occurred handler + + @param message: The poweroff timeout occurred message string + @return: None + """ + if len(message["message"]) < 16: + self.logger.debug("Poweroff message id detected, but was the wrong length.") + return + + mode = struct.unpack('h', bytearray( + message["message"][MsgFieldPositions.START_POS_FIELD_1: MsgFieldPositions.START_POS_FIELD_1 + 2])) + + if len(mode) > 0: + self.poweroff_timeout_expired = bool(mode[0]) + self.td_power_off_timestamp = timestamp + + def cmd_off_button_override(self, state: int, reset: int = NO_RESET) -> int: + """ + Constructs and sends the Off button override command + See TD/App/Controllers/Buttons.c + Constraints: + Must be logged into TD. + Given state must be a 0 or 1. + + @param state: integer - 1 to override off button to "pressed", 0 to "released" + @param reset: integer - 1 to reset a previous override, 0 to override + @return: 1 if successful, zero otherwise + """ + + rst = integer_to_bytearray(reset) + sta = integer_to_bytearray(state) + payload = rst + sta + + message = DenaliMessage.build_message(channel_id=DenaliChannels.dialin_to_td_ch_id, + message_id=MsgIds.MSG_ID_TD_OFF_BUTTON_OVERRIDE_REQUEST.value, + payload=payload) + + self.logger.debug("override off button") + + # 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 = ("pressed" if state != 0 else "released") + + self.logger.debug("Off button 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_stop_button_override(self, state: int, reset: int = NO_RESET) -> int: + """ + Constructs and sends the Stop button override command + See TD/App/Controllers/Buttons.c + Constraints: + Must be logged into TD. + Given state must be a 0 or 1. + + @param state: integer - 1 to override stop button to "pressed", 0 to "released" + @param reset: integer - 1 to reset a previous override, 0 to override + @return: 1 if successful, zero otherwise + """ + + rst = integer_to_bytearray(reset) + sta = integer_to_bytearray(state) + payload = rst + sta + + message = DenaliMessage.build_message(channel_id=DenaliChannels.dialin_to_td_ch_id, + message_id=MsgIds.MSG_ID_TD_STOP_BUTTON_OVERRIDE_REQUEST.value, + payload=payload) + + self.logger.debug("override stop button") + + # 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 = ("pressed" if state != 0 else "released") + self.logger.debug("Stop button 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/modules/pressure_sensors.py =================================================================== diff -u --- leahi-dialin/td/modules/pressure_sensors.py (revision 0) +++ leahi-dialin/td/modules/pressure_sensors.py (revision 4e2e867e7ee1fcf889b286167a3d6bb7f4fefb80) @@ -0,0 +1,192 @@ +########################################################################### +# +# 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 pressure_occlusion.py +# +# @author (last) Vinayakam Mani +# @date (last) 02-May-2024 +# @author (original) Peter Lucia +# @date (original) 02-Apr-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 TDPressureOcclusion(AbstractSubSystem): + """ + Treatment Delivery (TD) Dialin API sub-class for pressure related commands. + """ + + def __init__(self, can_interface, logger: Logger): + """ + TDPressureOcclusion 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_PRESSURE_DATA.value + self.can_interface.register_receiving_publication_function(channel_id, msg_id, + self._handler_pressure_sync) + + self.td_pressure_occlusion_timestamp = 0.0 + self.arterial_pressure = 0.0 + self.venous_pressure = 0.0 + self.arterial_long_filtered_pressure = 0.0 + self.venous_long_filtered_pressure = 0.0 + self.pressure_limits_state = 0 + self.arterial_pressure_limit_min = 0 + self.arterial_pressure_limit_max = 0 + self.venous_pressure_limit_min = 0 + self.venous_pressure_limit_max = 0 + + @publish([ + "td_pressure_occlusion_timestamp", + "arterial_pressure", + "venous_pressure", + "pressure_limits_state", + "arterial_pressure_limit_min", + "arterial_pressure_limit_max", + "venous_pressure_limit_min", + "venous_pressure_limit_max", + "arterial_long_filtered_pressure", + "venous_long_filtered_pressure", + ]) + def _handler_pressure_sync(self, message, timestamp=0.0): + """ + Handles published pressure & occlusion data messages. Pressure data are captured + for reference. + + @param message: published pressure & occlusion data message + @return: none + """ + + art = struct.unpack('f', bytearray( + message['message'][MsgFieldPositions.START_POS_FIELD_1:MsgFieldPositions.END_POS_FIELD_1])) + ven = struct.unpack('f', bytearray( + message['message'][MsgFieldPositions.START_POS_FIELD_2:MsgFieldPositions.END_POS_FIELD_2])) + pls = struct.unpack('i', bytearray( + message['message'][MsgFieldPositions.START_POS_FIELD_3:MsgFieldPositions.END_POS_FIELD_3])) + apl = struct.unpack('i', bytearray( + message['message'][MsgFieldPositions.START_POS_FIELD_4:MsgFieldPositions.END_POS_FIELD_4])) + apu = struct.unpack('i', bytearray( + message['message'][MsgFieldPositions.START_POS_FIELD_5:MsgFieldPositions.END_POS_FIELD_5])) + vpl = struct.unpack('i', bytearray( + message['message'][MsgFieldPositions.START_POS_FIELD_6:MsgFieldPositions.END_POS_FIELD_6])) + vpu = struct.unpack('i', bytearray( + message['message'][MsgFieldPositions.START_POS_FIELD_7:MsgFieldPositions.END_POS_FIELD_7])) + lfa = struct.unpack('f', bytearray( + message['message'][MsgFieldPositions.START_POS_FIELD_8:MsgFieldPositions.END_POS_FIELD_8])) + lfv = struct.unpack('f', bytearray( + message['message'][MsgFieldPositions.START_POS_FIELD_9:MsgFieldPositions.END_POS_FIELD_9])) + + self.arterial_pressure = art[0] + self.venous_pressure = ven[0] + self.pressure_limits_state = pls[0] + self.arterial_pressure_limit_min = apl[0] + self.arterial_pressure_limit_max = apu[0] + self.venous_pressure_limit_min = vpl[0] + self.venous_pressure_limit_max = vpu[0] + self.arterial_long_filtered_pressure = lfa[0] + self.venous_long_filtered_pressure = lfv[0] + self.td_pressure_occlusion_timestamp = timestamp + + def cmd_pressure_sensor_override(self, pressure: float, sensor: int, reset: int = NO_RESET) -> int: + """ + Constructs and sends the measured arterial pressure override command + PRESSURE_SENSOR_ARTERIAL = 0 ///< Arterial blood line pressure sensor + PRESSURE_SENSOR_VENOUS = 1 ///< Vensous blood line pressure sensor + Constraints: + Must be logged into TD. + + @param pressure: float - measured arterial pressure (in mmHg) to override with + @param sensor: bool - switch between filtered override and raw override + @param reset: integer - 1 to reset a previous override, 0 to override + @return: 1 if successful, zero otherwise + """ + + rst = integer_to_bytearray(reset) + sen = integer_to_bytearray(sensor) + prs = float_to_bytearray(pressure) + payload = rst + prs + sen + + message = DenaliMessage.build_message(channel_id=DenaliChannels.dialin_to_TD_ch_id, + message_id=MsgIds.MSG_ID_TD_PRESSURE_OVERRIDE_REQUEST.value, + payload=payload) + + self.logger.debug("override pressure sensor") + + # Send message + received_message = self.can_interface.send(message) + + # If there is content...S + if received_message is not None: + if reset == RESET: + str_res = "reset back to normal. " + else: + str_res = str(pres) + " mmHg. " + self.logger.debug("pressure (measured)) 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_pressure_sensors_broadcast_interval_override(self, ms: int, reset: int = NO_RESET) -> int: + """ + Constructs and sends the measured pressure sensors 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_PRESSURE_PUBLISH_INTERVAL_OVERRIDE_REQUEST.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 \ No newline at end of file Index: leahi-dialin/td/modules/switches.py =================================================================== diff -u --- leahi-dialin/td/modules/switches.py (revision 0) +++ leahi-dialin/td/modules/switches.py (revision 4e2e867e7ee1fcf889b286167a3d6bb7f4fefb80) @@ -0,0 +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/modules/valves.py =================================================================== diff -u --- leahi-dialin/td/modules/valves.py (revision 0) +++ leahi-dialin/td/modules/valves.py (revision 4e2e867e7ee1fcf889b286167a3d6bb7f4fefb80) @@ -0,0 +1,302 @@ +########################################################################### +# +# 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 valves.py +# +# @author (last) James Walter Taylor +# @date (last) 03-Aug-2023 +# @author (original) Dara Navaei +# @date (original) 19-Aug-2020 +# +############################################################################ + + +import struct +from enum import unique +from logging import Logger + +from .constants import NO_RESET +from ..common import MsgIds +from ..protocols.CAN import DenaliMessage, DenaliChannels +from ..utils.base import AbstractSubSystem, publish, DialinEnum +from ..utils.checks import check_broadcast_interval_override_ms +from ..utils.conversions import integer_to_bytearray, float_to_bytearray + + +@unique +class ValvesEnum(DialinEnum): + VBA = 0 + VBV = 1 + + +@unique +class ValvesPositions(DialinEnum): + VALVE_POSITION_NOT_IN_POSITION = 0 + VALVE_POSITION_A_INSERT_EJECT = 1 + VALVE_POSITION_B_OPEN = 2 + VALVE_POSITION_C_CLOSE = 3 + + +@unique +class ValvesStates(DialinEnum): + VALVE_STATE_WAIT_FOR_POST = 0 + VALVE_STATE_HOMING_NOT_STARTED = 1 + VALVE_STATE_HOMING_FIND_ENERGIZED_EDGE = 2 + VALVE_STATE_HOMING_FIND_DEENERGIZED_EDGE = 3 + VALVE_STATE_IDLE = 4 + VALVE_STATE_IN_TRANSITION = 5 + +class TDValves(AbstractSubSystem): + """ + Hemodialysis Device (HD) Dialin API sub-class for valves related commands. + """ + # Valves states publish message field positions + # Note the MsgFieldPosition was not used since some of the published data are S16 + START_POS_VALVES_ID = DenaliMessage.PAYLOAD_START_INDEX + END_POS_VALVES_ID = START_POS_VALVES_ID + 4 + + START_VALVES_STATE = END_POS_VALVES_ID + END_VALVES_STATE = START_VALVES_STATE + 4 + + START_POS_VALVES_CURR_POS_ID = END_VALVES_STATE + END_POS_VALVES_CURR_POS_ID = START_POS_VALVES_CURR_POS_ID + 4 + + START_POS_VALVES_CURR_POS = END_POS_VALVES_CURR_POS_ID + END_POS_VALVES_CURR_POS = START_POS_VALVES_CURR_POS + 2 + + START_POS_VALVES_NEXT_POS = END_POS_VALVES_CURR_POS + END_POS_VALVES_NEXT_POS = START_POS_VALVES_NEXT_POS + 2 + + + def __init__(self, can_interface, logger: Logger): + """ + DGDrainPump constructor + + @param can_interface: (DenaliCanMessenger) - Denali CAN messenger object. + @param logger: (Logger) - Dialin logger + """ + 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_VALVES_DATA.value + self.can_interface.register_receiving_publication_function(channel_id, msg_id, + self._handler_valves_sync) + + self.td_valves_timestamp = 0.0 + # A dictionary of the valves with the status + self.valves_status = {ValvesEnum.VBA.name: {}, + ValvesEnum.VBV.name: {}} + + def cmd_valves_broadcast_interval_override(self, ms: int, reset: int = NO_RESET) -> int: + """ + Constructs and sends broadcast time interval + 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: Publish time interval in ms + @param reset: integer - 1 to reset a previous override, 0 to override + @returns 1 if successful, zero otherwise + """ + if not check_broadcast_interval_override_ms(ms): + return False + + reset_value = integer_to_bytearray(reset) + interval_value = integer_to_bytearray(ms) + payload = reset_value + interval_value + + message = DenaliMessage.build_message(channel_id=DenaliChannels.dialin_to_hd_ch_id, + message_id=MsgIds.MSG_ID_TD_VALVES_PUBLISH_INTERVAL_OVERRIDE_REQUEST.value, + payload=payload) + + self.logger.debug("Sending {} ms publish interval to the HD valves module".format(ms)) + # Send message + received_message = self.can_interface.send(message) + + # If there is content in message + if received_message is not None: + # Response payload is OK or not + return received_message['message'][DenaliMessage.PAYLOAD_START_INDEX] + else: + self.logger.debug("Timeout!!!!") + return False + + def cmd_set_valve_position(self, valve: int, position: int, reset: int = NO_RESET) -> int: + """ + Constructs and sends the TD valves set position for a valve + + @param valve: integer - Valve number. Defined in ValvesEnum class + @param position: integer - Position number: Defined in ValvesPositions class + @param reset: integer - 1 to reset a previous override, 0 to override + @returns 1 if successful, zero otherwise + """ + reset_value = integer_to_bytearray(reset) + vlv = integer_to_bytearray(valve) + pos = integer_to_bytearray(position) + payload = reset_value + pos + vlv + + message = DenaliMessage.build_message(channel_id=DenaliChannels.dialin_to_hd_ch_id, + message_id=MsgIds.MSG_ID_TD_PINCH_VALVE_SET_POSITION_REQUEST.value, + payload=payload) + # 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("TD cmd_valve_override Timeout!!!") + return False + + def cmd_home_valve(self, valve: int) -> int: + """ + Constructs and sends the TD valves home command + + @param valve: integer - Valve number. Defined in ValvesEnum class + @returns 1 if successful, zero otherwise + """ + payload = integer_to_bytearray(valve) + + message = DenaliMessage.build_message(channel_id=DenaliChannels.dialin_to_hd_ch_id, + message_id=MsgIds.MSG_ID_TD_PINCH_VALVE_HOME_REQUEST.value, + payload=payload) + # 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("TD Homing Valve Timeout!!!") + return False + + def cmd_valve_encoder_position_override(self, valve: int, position_count: int, reset: int = NO_RESET) -> int: + """ + Constructs and sends the TD valves set position for a valve + + @@param valve: integer - Valve number. Defined in ValvesEnum class + @param position_count: integer value + @param reset: integer - 1 to reset a previous override, 0 to override + @returns 1 if successful, zero otherwise + """ + reset_value = integer_to_bytearray(reset) + vlv = integer_to_bytearray(valve) + pos = integer_to_bytearray(position_count) + payload = reset_value + pos + vlv + + message = DenaliMessage.build_message(channel_id=DenaliChannels.dialin_to_hd_ch_id, + message_id=MsgIds.MSG_ID_TD_ROTARY_PINCH_VALVE_POSITION_OVERRIDE_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("Setting {} position to {} ".format(str(ValvesEnum(valve).name), position_count)) + + # response payload is OK or not OK + return received_message['message'][DenaliMessage.PAYLOAD_START_INDEX] + else: + self.logger.debug("TD valve position override Timeout!!!") + return False + + def cmd_valve_status_override(self, valve: int, status: int, reset: int = NO_RESET) -> int: + """ + Constructs and sends the TD valves set position for a valve + + @@param valve: integer - Valve number. Defined in ValvesEnum class + @param status: integer value + @param status: integer value + @param reset: integer - 1 to reset a previous override, 0 to override + @returns 1 if successful, zero otherwise + """ + reset_value = integer_to_bytearray(reset) + vlv = integer_to_bytearray(valve) + sts = integer_to_bytearray(status) + payload = reset_value + sts + vlv + + message = DenaliMessage.build_message(channel_id=DenaliChannels.dialin_to_hd_ch_id, + message_id=MsgIds.MSG_ID_TD_ROTARY_PINCH_VALVE_STATUS_OVERRIDE_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("Setting {} status to {} ".format(str(ValvesEnum(valve).name), status)) + + # response payload is OK or not OK + return received_message['message'][DenaliMessage.PAYLOAD_START_INDEX] + else: + self.logger.debug("TD valve status override Timeout!!!") + return False + + def cmd_valve_modify_encoder_position_by_offset(self, valve: int, counts: int) -> int: + """ + Constructs and sends a given valve to change position by a + given number of encoder counts. + + @@param valve: integer - Valve number. Defined in ValvesEnum class + @param count: integer value + @param reset: integer - 1 to reset a previous override, 0 to override + @returns 1 if successful, zero otherwise + """ + vlv = integer_to_bytearray(valve) + pos = integer_to_bytearray(counts) + payload = pos + vlv + + message = DenaliMessage.build_message(channel_id=DenaliChannels.dialin_to_hd_ch_id, + message_id=MsgIds.MSG_ID_TD_ROTARY_PINCH_VALVE_POSITION_OVERRIDE_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("Setting {} position by {} ".format(str(ValvesEnum(valve).name), counts)) + + # response payload is OK or not OK + return received_message['message'][DenaliMessage.PAYLOAD_START_INDEX] + else: + self.logger.debug("TD valve position override Timeout!!!") + return False + + @publish(["td_valves_timestamp","valves_status"]) + def _handler_valves_sync(self, message: dict, timestamp=0.0) -> None: + """ + Handles published valves data messages. TD valves data are captured + for reference. + + @param message: published TD valves data message + @returns none + """ + vlv_id = struct.unpack('i', bytearray( + message['message'][self.START_POS_VALVES_ID:self.END_POS_VALVES_ID]))[0] + state_id = struct.unpack('i', bytearray( + message['message'][self.START_VALVES_STATE:self.END_VALVES_STATE]))[0] + pos_id = struct.unpack('i', bytearray( + message['message'][self.START_POS_VALVES_CURR_POS_ID:self.END_POS_VALVES_CURR_POS_ID]))[0] + pos_cnt = struct.unpack('h', bytearray( + message['message'][self.START_POS_VALVES_CURR_POS:self.END_POS_VALVES_CURR_POS]))[0] + next_pos = struct.unpack('h', bytearray( + message['message'][self.START_POS_VALVES_NEXT_POS:self.END_POS_VALVES_NEXT_POS]))[0] + + # To make sure values of the enums are not out of range + if ValvesEnum.has_value(vlv_id) and ValvesPositions.has_value(pos_id) and ValvesStates.has_value(pos_id): + vlv_name = ValvesEnum(vlv_id).name + # Update the valves dictionary + self.valves_status[vlv_name] = {'Valve': vlv_name, 'PosID': ValvesPositions(pos_id).name, 'PosCnt': pos_cnt, + 'Cmd': next_pos, 'State': ValvesStates(state_id).name} + self.td_valves_timestamp = timestamp \ No newline at end of file Index: leahi-dialin/td/modules/voltages.py =================================================================== diff -u --- leahi-dialin/td/modules/voltages.py (revision 0) +++ leahi-dialin/td/modules/voltages.py (revision 4e2e867e7ee1fcf889b286167a3d6bb7f4fefb80) @@ -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 Fisheye: Tag 4e2e867e7ee1fcf889b286167a3d6bb7f4fefb80 refers to a dead (removed) revision in file `leahi-dialin/td/pressure_sensors.py'. Fisheye: No comparison available. Pass `N' to diff? Index: leahi-dialin/td/proxy/__init__.py =================================================================== diff -u --- leahi-dialin/td/proxy/__init__.py (revision 0) +++ leahi-dialin/td/proxy/__init__.py (revision 4e2e867e7ee1fcf889b286167a3d6bb7f4fefb80) @@ -0,0 +1 @@ \ No newline at end of file Index: leahi-dialin/td/proxy/dd_proxy.py =================================================================== diff -u --- leahi-dialin/td/proxy/dd_proxy.py (revision 0) +++ leahi-dialin/td/proxy/dd_proxy.py (revision 4e2e867e7ee1fcf889b286167a3d6bb7f4fefb80) @@ -0,0 +1 @@ \ No newline at end of file Index: leahi-dialin/td/proxy/ro_proxy.py =================================================================== diff -u --- leahi-dialin/td/proxy/ro_proxy.py (revision 0) +++ leahi-dialin/td/proxy/ro_proxy.py (revision 4e2e867e7ee1fcf889b286167a3d6bb7f4fefb80) @@ -0,0 +1 @@ \ No newline at end of file Fisheye: Tag 4e2e867e7ee1fcf889b286167a3d6bb7f4fefb80 refers to a dead (removed) revision in file `leahi-dialin/td/switches.py'. Fisheye: No comparison available. Pass `N' to diff? Index: leahi-dialin/td/treatment_delivery.py =================================================================== diff -u -r873e727b657db3d14625ca0de499bfd630cbe502 -r4e2e867e7ee1fcf889b286167a3d6bb7f4fefb80 --- leahi-dialin/td/treatment_delivery.py (.../treatment_delivery.py) (revision 873e727b657db3d14625ca0de499bfd630cbe502) +++ leahi-dialin/td/treatment_delivery.py (.../treatment_delivery.py) (revision 4e2e867e7ee1fcf889b286167a3d6bb7f4fefb80) @@ -15,16 +15,16 @@ ############################################################################ import struct -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 .modules.air_pump import TDAirPump +from .modules.air_trap import TDAirTrap +from .modules.alarms import TDAlarms +from .modules.blood_flow import TDBloodFlow +from .modules.bubble_detector import TDBubbleDectector +from .modules.buttons import TDButtons +from .pmodules.ressure_sensors import TDPressureSensors +from .modules.switches import TDSwitches +from .modules.valves import TDValves +from .modules.voltages import TDVoltages from ..common.msg_defs import MsgIds, MsgFieldPositions from ..protocols.CAN import DenaliMessage, DenaliCanMessenger, DenaliChannels from ..utils.base import AbstractSubSystem, publish, LogManager Fisheye: Tag 4e2e867e7ee1fcf889b286167a3d6bb7f4fefb80 refers to a dead (removed) revision in file `leahi-dialin/td/valves.py'. Fisheye: No comparison available. Pass `N' to diff? Fisheye: Tag 4e2e867e7ee1fcf889b286167a3d6bb7f4fefb80 refers to a dead (removed) revision in file `leahi-dialin/td/voltages.py'. Fisheye: No comparison available. Pass `N' to diff?