Index: leahi_dialin/dd/dialysate_delivery.py =================================================================== diff -u -r2dab2b0329a56006e07cd36a3883ed099d7a367a -rcf5e82faaf502b2828ffb6e43c2a64b71f6e31f2 --- leahi_dialin/dd/dialysate_delivery.py (.../dialysate_delivery.py) (revision 2dab2b0329a56006e07cd36a3883ed099d7a367a) +++ leahi_dialin/dd/dialysate_delivery.py (.../dialysate_delivery.py) (revision cf5e82faaf502b2828ffb6e43c2a64b71f6e31f2) @@ -15,6 +15,7 @@ ############################################################################ import struct +from .modules.alarms import DDAlarms from .modules.balancing_chamber import DDBalancingChamber from .modules.concentrate_pump import DDConcentratePumps from .modules.conductivity_sensors import DDConductivitySensors @@ -113,7 +114,7 @@ self.dd_last_debug_event = '' # Create command groups - + self.alarms = DDAlarms(self.can_interface, self.logger) self.balancing_chamber = DDBalancingChamber(self.can_interface, self.logger) self.concentrate_pumps = DDConcentratePumps(self.can_interface, self.logger) self.conductivity_sensors = DDConductivitySensors(self.can_interface, self.logger) Index: leahi_dialin/dd/modules/alarms.py =================================================================== diff -u --- leahi_dialin/dd/modules/alarms.py (revision 0) +++ leahi_dialin/dd/modules/alarms.py (revision cf5e82faaf502b2828ffb6e43c2a64b71f6e31f2) @@ -0,0 +1,127 @@ +########################################################################### +# +# 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) Micahel Garthwaite +# @date (last) 17-Aug-2023 +# @author (original) Quang Nguyen +# @date (original) 02-Sep-2020 +# +############################################################################ +import struct +from logging import Logger + +from .constants import RESET, NO_RESET +from leahi_dialin.common.dd_defs import DDEventDataType +from leahi_dialin.common.msg_defs import MsgIds, MsgFieldPositions +from leahi_dialin.protocols.CAN import DenaliMessage, DenaliChannels +from leahi_dialin.utils.base import AbstractSubSystem, publish, DialinEnum +from leahi_dialin.utils.checks import check_broadcast_interval_override_ms +from leahi_dialin.utils.conversions import integer_to_bytearray, float_to_bytearray + +class DDAlarms(AbstractSubSystem): + """ + DD interface containing alarm related commands. + """ + _ALARM_ID_MAX_ALARMS = 500 + START_POS_ALARM_ID = DenaliMessage.PAYLOAD_START_INDEX + END_POS_ALARM_ID = START_POS_ALARM_ID + 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.fp_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_triggered) + + self.dd_alarm_triggered_timestamp = 0.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 + + self.last_alarm_triggered = 0 + self.last_alarm_data_1 = 0.0 + self.last_alarm_data_2 = 0.0 + + # Loop through the list of the event data type enum and update the dictionary + for data_type in DDEventDataType: + event_data_type = DDEventDataType(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 + + @publish(["dd_alarm_triggered_timestamp", "alarm_states", "alarm_conditions", "alarm_data", + "alarm_priorities", "alarm_ranks", "alarm_clear_top_only_flags"]) + def _handler_alarm_triggered(self, message, timestamp = 0.0): + """ + Handles published DD alarm activation messages. + + @param message: published DD 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[DDEventDataType(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[DDEventDataType(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.last_alarm_triggered = alarm_id + self.last_alarm_data_1 = data_1[0] + self.last_alarm_data_2 = data_2[0] + self.dd_alarm_triggered_timestamp = timestamp + + + def _handler_alarm_info(self, message, timestamp = 0.0): + pass + + + #TODO: alarm state override, alarm info pubish override Index: leahi_dialin/fp/filtration_purification.py =================================================================== diff -u -r68422d08c4141999a13496343264483a32314d37 -rcf5e82faaf502b2828ffb6e43c2a64b71f6e31f2 --- leahi_dialin/fp/filtration_purification.py (.../filtration_purification.py) (revision 68422d08c4141999a13496343264483a32314d37) +++ leahi_dialin/fp/filtration_purification.py (.../filtration_purification.py) (revision cf5e82faaf502b2828ffb6e43c2a64b71f6e31f2) @@ -15,6 +15,7 @@ ############################################################################ import struct +from .modules.alarms import FPAlarms from .modules.boost_pump import FPBoostPump from .modules.conductivity_sensors import FPConductivitySensors from .modules.constants import NO_RESET, RESET @@ -109,6 +110,7 @@ self.fp_last_debug_event = '' # Create command groups + self.alarms = FPAlarms(self.can_interface, self.logger) self.boost_pump = FPBoostPump(self.can_interface, self.logger) self.conductivity = FPConductivitySensors(self.can_interface, self.logger) self.flows = FPFlowSensors(self.can_interface, self.logger) Index: leahi_dialin/fp/modules/alarms.py =================================================================== diff -u --- leahi_dialin/fp/modules/alarms.py (revision 0) +++ leahi_dialin/fp/modules/alarms.py (revision cf5e82faaf502b2828ffb6e43c2a64b71f6e31f2) @@ -0,0 +1,127 @@ +########################################################################### +# +# 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) Micahel Garthwaite +# @date (last) 17-Aug-2023 +# @author (original) Quang Nguyen +# @date (original) 02-Sep-2020 +# +############################################################################ +import struct +from logging import Logger + +from .constants import RESET, NO_RESET +from leahi_dialin.common.fp_defs import FPEventDataType +from leahi_dialin.common.msg_defs import MsgIds, MsgFieldPositions +from leahi_dialin.protocols.CAN import DenaliMessage, DenaliChannels +from leahi_dialin.utils.base import AbstractSubSystem, publish, DialinEnum +from leahi_dialin.utils.checks import check_broadcast_interval_override_ms +from leahi_dialin.utils.conversions import integer_to_bytearray, float_to_bytearray + +class FPAlarms(AbstractSubSystem): + """ + FP interface containing alarm related commands. + """ + _ALARM_ID_MAX_ALARMS = 500 + START_POS_ALARM_ID = DenaliMessage.PAYLOAD_START_INDEX + END_POS_ALARM_ID = START_POS_ALARM_ID + 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.fp_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_triggered) + + self.fp_alarm_triggered_timestamp = 0.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 + + self.last_alarm_triggered = 0 + self.last_alarm_data_1 = 0.0 + self.last_alarm_data_2 = 0.0 + + # Loop through the list of the event data type enum and update the dictionary + for data_type in FPEventDataType: + event_data_type = FPEventDataType(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 + + @publish(["fp_alarm_triggered_timestamp", "alarm_states", "alarm_conditions", "alarm_data", + "alarm_priorities", "alarm_ranks", "alarm_clear_top_only_flags"]) + def _handler_alarm_triggered(self, message, timestamp = 0.0): + """ + Handles published FP alarm activation messages. + + @param message: published FP 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[FPEventDataType(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[FPEventDataType(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.last_alarm_triggered = alarm_id + self.last_alarm_data_1 = data_1[0] + self.last_alarm_data_2 = data_2[0] + self.fp_alarm_triggered_timestamp = timestamp + + + def _handler_alarm_info(self, message, timestamp = 0.0): + pass + + + #TODO: alarm state override, alarm info pubish override Index: leahi_dialin/fp/modules/boost_pump.py =================================================================== diff -u -r7628b7a718a1b52c33f58a6003ef13cba4db5e5d -rcf5e82faaf502b2828ffb6e43c2a64b71f6e31f2 --- leahi_dialin/fp/modules/boost_pump.py (.../boost_pump.py) (revision 7628b7a718a1b52c33f58a6003ef13cba4db5e5d) +++ leahi_dialin/fp/modules/boost_pump.py (.../boost_pump.py) (revision cf5e82faaf502b2828ffb6e43c2a64b71f6e31f2) @@ -14,10 +14,10 @@ # ############################################################################ import struct -from enum import unique + from logging import Logger -from enum import unique + from leahi_dialin.utils.base import DialinEnum from .constants import RESET, NO_RESET from leahi_dialin.common.msg_defs import MsgIds, MsgFieldPositions @@ -138,7 +138,7 @@ Constraints: Must be logged into FP. - @param pressure: float - target pressure in PSI + @param pressure: float - target pressure in PSI between 10 - 40 @param reset: integer - 1 to reset a previous override, 0 to override @return: 1 if successful, zero otherwise """ @@ -176,7 +176,7 @@ Constraints: Must be logged into FP. - @param flow: float - target flow in mL/min + @param flow: float - target flow in mL/min between 0 - 1500 @param reset: integer - 1 to reset a previous override, 0 to override @return: 1 if successful, zero otherwise """ @@ -214,7 +214,7 @@ Constraints: Must be logged into FP. - @param pressure: float - target duty cycle between 0 - 0.99 + @param pressure: float - target duty cycle between 0 - 0.90 @param reset: integer - 1 to reset a previous override, 0 to override @return: 1 if successful, zero otherwise """ Index: leahi_dialin/fp/modules/ro_pump.py =================================================================== diff -u -r7628b7a718a1b52c33f58a6003ef13cba4db5e5d -rcf5e82faaf502b2828ffb6e43c2a64b71f6e31f2 --- leahi_dialin/fp/modules/ro_pump.py (.../ro_pump.py) (revision 7628b7a718a1b52c33f58a6003ef13cba4db5e5d) +++ leahi_dialin/fp/modules/ro_pump.py (.../ro_pump.py) (revision cf5e82faaf502b2828ffb6e43c2a64b71f6e31f2) @@ -141,7 +141,7 @@ Constraints: Must be logged into FP. - @param pressure: float - target pressure in PSI + @param pressure: float - target pressure in PSI between 10 - 120 @param reset: integer - 1 to reset a previous override, 0 to override @return: 1 if successful, zero otherwise """ @@ -179,7 +179,7 @@ Constraints: Must be logged into FP. - @param flow: float - target flow in mL/min + @param flow: int - target flow in mL/min between 0 - 1500 @param reset: integer - 1 to reset a previous override, 0 to override @return: 1 if successful, zero otherwise """ @@ -217,7 +217,7 @@ Constraints: Must be logged into FP. - @param pressure: float - target duty cycle between 0 - 0.99 + @param pressure: float - target duty cycle between 0 - 0.90 @param reset: integer - 1 to reset a previous override, 0 to override @return: 1 if successful, zero otherwise """ @@ -248,7 +248,7 @@ self.logger.debug("Timeout!!!!") return False - def cmd_boost_pump_set_hard_stop(self) -> int: + def cmd_ro_pump_set_hard_stop(self) -> int: """ Constructs and sends the hard stop for the RO pump. This will stop the RO pump. Index: leahi_dialin/td/modules/alarms.py =================================================================== diff -u -rafe332fec54d9d0432dfc0d54aef1debaa92b066 -rcf5e82faaf502b2828ffb6e43c2a64b71f6e31f2 --- leahi_dialin/td/modules/alarms.py (.../alarms.py) (revision afe332fec54d9d0432dfc0d54aef1debaa92b066) +++ leahi_dialin/td/modules/alarms.py (.../alarms.py) (revision cf5e82faaf502b2828ffb6e43c2a64b71f6e31f2) @@ -121,6 +121,10 @@ # alarm debug data on alarm triggered message self.alarm_data = [0, 0] * 500 + self.last_alarm_triggered = 0 + self.last_alarm_data_1 = 0.0 + self.last_alarm_data_2 = 0.0 + # alarm information self.alarm_data_type = dict() self.alarm_volume = 0 @@ -209,6 +213,9 @@ 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.last_alarm_triggered = alarm_id + self.last_alarm_data_1 = data_1[0] + self.last_alarm_data_2 = data_2[0] self.td_alarm_triggered_timestamp = timestamp @publish(["TD_alarm_cleared_timestamp", "alarm_states", "alarm_conditions"]) Index: tests/fp_dianogistic_logger.py =================================================================== diff -u -r68422d08c4141999a13496343264483a32314d37 -rcf5e82faaf502b2828ffb6e43c2a64b71f6e31f2 --- tests/fp_dianogistic_logger.py (.../fp_dianogistic_logger.py) (revision 68422d08c4141999a13496343264483a32314d37) +++ tests/fp_dianogistic_logger.py (.../fp_dianogistic_logger.py) (revision cf5e82faaf502b2828ffb6e43c2a64b71f6e31f2) @@ -64,7 +64,7 @@ print ("Starting threads...") float_control_thread = threading.Thread(target=fp_float_controller, args=(fp, CONTROL_INTERVAL),daemon=True) - #float_control_thread.start() + float_control_thread.start() pressure_log_writer_thread = threading.Thread(target=fp_pressure_recorder, args=(fp, RECORDING_INTERVAL), daemon=True) temp_log_writer_thread = threading.Thread(target=fp_temperature_recorder, args=(fp, RECORDING_INTERVAL), daemon=True)