Index: dialin/common/msg_defs.py =================================================================== diff -u -r187980e6e0c0c490e6ed0f8c6831c8e44086586f -r18c04cbcd10619250868cec39470a2b29c86121b --- dialin/common/msg_defs.py (.../msg_defs.py) (revision 187980e6e0c0c490e6ed0f8c6831c8e44086586f) +++ dialin/common/msg_defs.py (.../msg_defs.py) (revision 18c04cbcd10619250868cec39470a2b29c86121b) @@ -77,8 +77,8 @@ MSG_ID_DG_HEAT_DISINFECT_DATA = 0x37 # DG heat disinfection publish data MSG_ID_UI_START_TREATMENT = 0x38 # UI user request to initiate a treatment MSG_ID_HD_START_TREATMENT_RESPONSE = 0x39 # HD response to user request to initiate a treatment - MSG_ID_HD_VALVES_DATA = 0x3B # HD broadcast of valves data - MSG_ID_UI_USER_CONFIRM_TREATMENT_PARAMS = 0x3A # UI user confirmation of treatment parameters + MSG_ID_HD_VALVES_DATA = 0x3A # HD broadcast of valves data + MSG_ID_UI_USER_CONFIRM_TREATMENT_PARAMS = 0x3B # UI user confirmation of treatment parameters MSG_ID_UI_TREATMENT_END_REQUEST = 0x3C # UI user treatment end request MSG_ID_HD_TREATMENT_END_RESPONSE = 0x3D # HD response to user request to end treatment MSG_ID_HD_AIR_TRAP_DATA = 0x003E # HD broadcast of air trap data @@ -101,6 +101,8 @@ MSG_ID_UI_SET_UF_VOLUME_PARAMETER = 0x4F # UI request to validate new ultrafiltration volume treatment parameter MSG_ID_HD_SET_UF_VOLUME_PARAMETER_RESPONSE = 0x50 # HD response to new treatment parameters set + MSG_ID_CAN_ERROR_COUNT = 0x999 # test code in support of EMC testing + # service/test CAN messages MSG_ID_TESTER_LOGIN_REQUEST = 0x8000 # HD tester log-in MSG_ID_DIAL_OUT_FLOW_SET_PT_OVERRIDE = 0x8001 # Dialysate outlet flow set point override request @@ -198,16 +200,17 @@ MSG_ID_HEAT_DISINFECT_NO_OF_CYCLES_TO_RUN = 0xA020 # Heat disinfection number of cycles to run MSG_ID_HEAT_DISINFECT_PUBLISH_INTERVAL_OVERRIDE = 0xA021 # Heat disinfection data publish interval override request MSG_ID_DG_SOFTWARE_RESET_REQUEST = 0xA022 # DG reset request - MSG_ID_DG_OPERATION_MODE_REQUEST = 0xA023 # DG operation mode request + MSG_ID_DG_CONCENTRATE_PUMP_MEASURED_SPEED_OVERRIDE = 0xA023 # DG concentrate pump measured speed override request MSG_ID_CONCENTRATE_PUMP_TARGET_SPEED_OVERRIDE = 0xA024 # Concentrate pumps' target speed override request MSG_ID_UV_REACTORS_DATA_PUBLISH_INTERVAL_OVERRIDE = 0xA025 # UV reactors data publish interval override MSG_ID_CONCENTRATE_PUMP_STATE_CHANGE_REQUEST = 0xA026 # Concentrate pumps' state change request (on / off) MSG_ID_CONCENTRATE_PUMP_PUBLISH_INTERVAL_OVERRIDE = 0xA027 # Concentrate pumps' data publish interval override request - MSG_ID_DG_START_STOP_UV_REACTORS_OVERRIDE = 0xA028 # DG start/stop UV reactors override request + MSG_ID_DG_START_STOP_INLET_UV_REACTOR = 0xA028 # DG start/stop inlet UV reactor MSG_ID_DG_REQUEST_CALIBRATION_DATA = 0xA029 # Requests calibration data from DG - MSG_ID_DG_FANS_DATA_PUBLISH_INTERVAL_OVERRIDE = 0xA02A # DG fans data publish interval override request + MSG_ID_DG_FANS_DATA_PUBLISH_OVERRIDE = 0xA02A # Fans data publish interval override request + MSG_ID_DG_START_STOP_OUTLET_UV_REACTOR = 0xA02B # DG start/stop outlet UV reactor MSG_ID_DG_UV_REACTORS_HEALTH_OVERRIDE = 0xA02C # DG UV reactors health override request - MSG_ID_DG_THERMISTORS_DATA_PUBLISH_INTERVAL_OVERRIDE = 0xA02D # DG thermistors data publish interval override + MSG_ID_DG_THERMISTORS_DATA_PUBLISH_INTERVAL_OVERRIDE = 0xA02D # DG thermistors data publish interval override MSG_ID_DG_THERMISTORS_VALUE_OVERRIDE = 0xA02E # DG thermistors value override MSG_ID_DG_RO_PUMP_DUTY_CYCLE_OVERRIDE = 0xA02F # DG RO pump duty cycle override MSG_ID_DG_RO_FLOW_RATE_OVERRIDE = 0xA030 # DG RO flow rate override @@ -218,6 +221,7 @@ MSG_ID_DG_DEBUG_EVENT = 0xFFF2 # DG debug event text to be logged in event log END_OF_MSG_IDS = 0xFFF3 # End of system message IDs + MSG_ID_ACK_MESSAGE_THAT_REQUIRES_ACK = 0XFFFF # Generic acknowledge @unique class RequestRejectReasons(DialinEnum): Index: dialin/dg/heaters.py =================================================================== diff -u -r812bbf1e5b3b2f1c4f5cfbef1264787e18afb5c3 -r18c04cbcd10619250868cec39470a2b29c86121b --- dialin/dg/heaters.py (.../heaters.py) (revision 812bbf1e5b3b2f1c4f5cfbef1264787e18afb5c3) +++ dialin/dg/heaters.py (.../heaters.py) (revision 18c04cbcd10619250868cec39470a2b29c86121b) @@ -20,122 +20,149 @@ from ..common.msg_defs import MsgIds, MsgFieldPositions from .constants import NO_RESET from ..protocols.CAN import (DenaliMessage, DenaliChannels) -from ..utils.base import _AbstractSubSystem, _publish +from ..utils.base import _AbstractSubSystem, _publish, DialinEnum from logging import Logger +from enum import unique +@unique +class HeatersStartStop(DialinEnum): + + STOP = 0 + START = 1 + + class Heaters(_AbstractSubSystem): + """ + Dialysate Generator (DG) Dialin API sub-class for heaters 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 - self.main_primary_heater_pwm = 0 - self.small_primary_heater_pwm = 0 - self.trimmer_heater_pwm = 0 + self.main_primary_heater_duty_cycle = 0 + self.small_primary_heater_duty_cycle = 0 + self.trimmer_heater_duty_cycle = 0 if self.can_interface is not None: channel_id = DenaliChannels.dg_sync_broadcast_ch_id msg_id = MsgIds.MSG_ID_DG_HEATERS_DATA.value self.can_interface.register_receiving_publication_function(channel_id, msg_id, self._handler_heaters_sync) - @_publish(["main_primary_heater_pwm", "small_primary_heater_pwm", "trimmer_heater_pwm"]) + def get_main_primary_heater_duty_cycle(self): + """ + Gets the main primary heater duty cycle + + @return: Main primary heater duty cycle + """ + return self.main_primary_heater_duty_cycle + + def get_small_primary_heater_duty_cycle(self): + """ + Gets the small primary heater duty cycle + + @return: Small primary heater duty cycle + """ + return self.small_primary_heater_duty_cycle + + def get_trimmer_heater_duty_cycle(self): + """ + Gets the trimmer heater duty cycle + + @return: Trimmer primary heater duty cycle + """ + return self.trimmer_heater_duty_cycle + + @_publish(["main_primary_heater_duty_cycle", "small_primary_heater_duty_cycle", "trimmer_heater_duty_cycle"]) def _handler_heaters_sync(self, message): """ Handles published heaters message @param message: published heaters data message @returns none """ - main_primary_heater_pwm = struct.unpack('i', bytearray( + main_primary_heater_pwm = struct.unpack('f', bytearray( message['message'][MsgFieldPositions.START_POS_FIELD_1:MsgFieldPositions.END_POS_FIELD_1])) - small_primary_heater_pwm = struct.unpack('i', bytearray( + small_primary_heater_pwm = struct.unpack('f', bytearray( message['message'][MsgFieldPositions.START_POS_FIELD_2:MsgFieldPositions.END_POS_FIELD_2])) - trimmer_heater_pwm = struct.unpack('i', bytearray( + trimmer_heater_pwm = struct.unpack('f', bytearray( message['message'][MsgFieldPositions.START_POS_FIELD_3:MsgFieldPositions.END_POS_FIELD_3])) - self.main_primary_heater_pwm = main_primary_heater_pwm[0] - self.small_primary_heater_pwm = small_primary_heater_pwm[0] - self.trimmer_heater_pwm = trimmer_heater_pwm[0] + self.main_primary_heater_duty_cycle = main_primary_heater_pwm[0] + self.small_primary_heater_duty_cycle = small_primary_heater_pwm[0] + self.trimmer_heater_duty_cycle = trimmer_heater_pwm[0] - def cmd_start_primary_heater(self): + def cmd_start_stop_primary_heater(self, state=HeatersStartStop.STOP.name): """ - Constructs and sends start primary heater command + Constructs and sends a start/stop primary heater command Constraints: A target temperature for primary heater between 10 and 90 deg C must have been given to DG previously. + @param state: (int) start/stop state of the primary heater. The default is stop. @returns none """ + if state == HeatersStartStop.START.name: + payload = integer_to_bytearray(1) + operation = 'Turning on ' + else: + payload = integer_to_bytearray(0) + operation = 'Turning off ' - # 1 is to start the primary heater - payload = integer_to_bytearray(1) message = DenaliMessage.build_message(channel_id=DenaliChannels.dialin_to_dg_ch_id, message_id=MsgIds.MSG_ID_START_STOP_PRIMARY_HEATER.value, payload=payload) - self.logger.debug("Starting the Primary heater") - self.can_interface.send(message, 0) - def cmd_stop_primary_heater(self): - """ - Constructs and sends stop primary heater command - - @returns none - """ - # 0 is to stop the primary heater - payload = integer_to_bytearray(0) - message = DenaliMessage.build_message(channel_id=DenaliChannels.dialin_to_dg_ch_id, - message_id=MsgIds.MSG_ID_START_STOP_PRIMARY_HEATER.value, - payload=payload) - self.logger.debug("Stopping the Primary heater") + self.logger.debug(operation + " the Primary heater") self.can_interface.send(message, 0) - def cmd_start_trimmer_heater(self): + def cmd_start_stop_trimmer_heater(self, state=HeatersStartStop.STOP.name): """ Constructs and sends start trimmer heater command Constraints: A target temperature for trimmer heater between 10 and 90 deg C must have been given to DG previously. + @param state: (int) start/stop state of the trimmer heater. The default is stop. @returns none """ - # 1 is to start the primary heater - payload = integer_to_bytearray(1) - message = DenaliMessage.build_message(channel_id=DenaliChannels.dialin_to_dg_ch_id, - message_id=MsgIds.MSG_ID_DG_START_STOP_TRIMMER_HEATER_CMD.value, - payload=payload) - self.logger.debug("Starting the Trimmer heater") - self.can_interface.send(message, 0) + if state == HeatersStartStop.START.name: + payload = integer_to_bytearray(1) + operation = 'Turning on ' + else: + payload = integer_to_bytearray(0) + operation = 'Turning off' - def cmd_stop_trimmer_heater(self): - """ - Constructs and sends stop trimmer heater command - - @returns none - """ - # 1 is to start the primary heater - payload = integer_to_bytearray(0) message = DenaliMessage.build_message(channel_id=DenaliChannels.dialin_to_dg_ch_id, message_id=MsgIds.MSG_ID_DG_START_STOP_TRIMMER_HEATER_CMD.value, payload=payload) - self.logger.debug("Stopping the Trimmer heater") + + self.logger.debug(operation + " the Primary heater") self.can_interface.send(message, 0) - def cmd_set_dialysate_target_temperature(self, pirmary_target_temp=37.0, trimmer_target_temp=38.0): + def cmd_set_dialysate_target_temperature(self, primary_target_temp=37.0, trimmer_target_temp=38.0): """ Constructs and sends primary and trimmer heater target temperature - @param pirmary_target_temp: Primary heater target temperature - @param trimmer_target_temp: Trimmer heater target temperature + @param primary_target_temp: (float) Primary heater target temperature + @param trimmer_target_temp: (float) Trimmer heater target temperature @returns none """ - primary_target = float_to_bytearray(pirmary_target_temp) + primary_target = float_to_bytearray(primary_target_temp) trimmer_target = float_to_bytearray(trimmer_target_temp) payload = primary_target + trimmer_target message = DenaliMessage.build_message(channel_id=DenaliChannels.dialin_to_dg_ch_id, message_id=MsgIds.MSG_ID_SET_DG_DIALYSATE_TEMP_TARGETS.value, payload=payload) - self.logger.debug("Setting Primary Heater to {} C and Trimmer Heater to {} C".format(pirmary_target_temp, trimmer_target_temp)) + self.logger.debug("Setting Primary Heater to {} C and Trimmer Heater to {} C".format(primary_target_temp, + trimmer_target_temp)) self.can_interface.send(message, 0) def cmd_heaters_broadcast_interval_override(self, ms, reset=NO_RESET): @@ -145,8 +172,8 @@ Must be logged into DG. Given interval must be non-zero and a multiple of the DG general task interval (50 ms). - @param ms: Publish time interval in ms - @param reset: integer - 1 to reset a previous override, 0 to override + @param ms: (int) Publish time interval in ms + @param reset: (int) 1 to reset a previous override, 0 to override @returns 1 if successful, zero otherwise """ resetValue = integer_to_bytearray(reset) Index: dialin/dg/thermistors.py =================================================================== diff -u -re851e75aa1d8f801324442b0cb034ba3e82325c9 -r18c04cbcd10619250868cec39470a2b29c86121b --- dialin/dg/thermistors.py (.../thermistors.py) (revision e851e75aa1d8f801324442b0cb034ba3e82325c9) +++ dialin/dg/thermistors.py (.../thermistors.py) (revision 18c04cbcd10619250868cec39470a2b29c86121b) @@ -76,9 +76,9 @@ Constraints: Must be logged into DG. - @param thermistor: (int) thermistor index - @param state: (int) start/stop state of the primary heater. The default is stop. @param value: float the temperature to be set + @param thermistor: (int) thermistor index + @param reset: (int) 1 to reset a previous override, 0 to override """ reset_value = integer_to_bytearray(reset) vlu = float_to_bytearray(value) Index: dialin/hd/alarms.py =================================================================== diff -u -r363867c876456821000c189f86fbb55b4d583c50 -r18c04cbcd10619250868cec39470a2b29c86121b --- dialin/hd/alarms.py (.../alarms.py) (revision 363867c876456821000c189f86fbb55b4d583c50) +++ dialin/hd/alarms.py (.../alarms.py) (revision 18c04cbcd10619250868cec39470a2b29c86121b) @@ -82,6 +82,11 @@ self.can_interface.register_receiving_publication_function(channel_id, msg_id, self._handler_alarm_clear) + channel_id = DenaliChannels.hd_alarm_broadcast_ch_id + 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) + # composite alarm status based on latest HD alarm status broadcast message self.alarms_state = 0 self.alarm_top = 0 @@ -91,8 +96,28 @@ # alarm states based on received HD alarm activation and alarm clear messages self.alarm_states = [False] * 500 + # alarm condition states based on received HD alarm activation and clear condition messages + self.alarm_conditions = [False] * 500 + def get_current_alarms_state(self): + """ + Gets the current alarms state. + None, Low, Medium, or High + @return: (str) the current alarms state in text form. + """ + result = "" + if self.alarms_state == self.HD_ALARM_STATE_NONE: + result = "None" + elif self.alarms_state == self.HD_ALARM_STATE_LOW: + result = "Low" + elif self.alarms_state == self.HD_ALARM_STATE_MEDIUM: + result = "Medium" + elif self.alarms_state == self.HD_ALARM_STATE_HIGH: + result = "High" + + return result + def get_alarm_states(self): """ Gets all states for all alarms @@ -101,6 +126,14 @@ """ return self.alarm_states + def get_alarm_conditions(self): + """ + Gets all alarm condition states for all alarms + + @return: List of booleans of size 500 + """ + return self.alarm_conditions + def get_alarms_top(self): """ Gets the top alarm @@ -125,7 +158,7 @@ """ return self.alarms_escalates_in - def get_alarms_flags(self): + def get_alarms_flags(self): # TODO - update flags to latest """ Gets the alarms flags @@ -138,16 +171,15 @@ No Rinseback = result & 16 No End Treatment = result & 32 No New Treatment = result & 64 - Bypass Dialyzer = result & 128 + User Must ACK = result & 128 Alarms to Escalate = result & 256 Alarms Silenced = result & 512 - TBD = result & 1024 + Alarm Lamp On = result & 1024 TBD = result & 2048 TBD = result & 4096 TBD = result & 8192 TBD = result & 16384 - TBD = result & 32768 - TBD = result & 65536 + Condition Detected = result & 32768 @return: (int) The alarms flags value """ @@ -204,6 +236,7 @@ alarm_id = struct.unpack('