Index: dialin/__init__.py =================================================================== diff -u -rb543d90a50cbef509d10f4c1412d87bf6eee2e4e -rc864e886bc1b52d3a43f03c507425248c44a913f --- dialin/__init__.py (.../__init__.py) (revision b543d90a50cbef509d10f4c1412d87bf6eee2e4e) +++ dialin/__init__.py (.../__init__.py) (revision c864e886bc1b52d3a43f03c507425248c44a913f) @@ -1,6 +1,6 @@ from .version import VERSION from .hd.hemodialysis_device import HD from .dg.dialysate_generator import DG -from .ui.hd_proxy import HDSimulator +from .ui.hd_simulator import HDSimulator __version__= VERSION \ No newline at end of file Index: dialin/common/msg_defs.py =================================================================== diff -u -rf8e83b59e5f3b903abb1cb78969d858439420f59 -rc864e886bc1b52d3a43f03c507425248c44a913f --- dialin/common/msg_defs.py (.../msg_defs.py) (revision f8e83b59e5f3b903abb1cb78969d858439420f59) +++ dialin/common/msg_defs.py (.../msg_defs.py) (revision c864e886bc1b52d3a43f03c507425248c44a913f) @@ -60,8 +60,10 @@ 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_UI_CONFIRM_TREATMENT = 0x3A # UI Confirms treatment parameters - MSG_ID_UI_END_TREATMENT = 0x3B # UI Ends a treatment + MSG_ID_UI_CONFIRM_TREATMENT = 0x3B # UI Confirms treatment parameters + MSG_ID_UI_END_TREATMENT = 0x3C # UI Ends a treatment + MSG_ID_UI_END_TREATMENT_RESPONSE = 0x3D # HD Responds to end treatment request + MSG_ID_UI_ALARM_ACKNOWLEDGE = 0x3F # UI Tells HD an alarm has been acknowledged MSG_ID_CAN_ERROR_COUNT = 0x999 # test code in support of EMC testing Index: dialin/dg/hd_proxy.py =================================================================== diff -u -r8ea13ae6dd10732bfcc456798f4785c4d88c95d3 -rc864e886bc1b52d3a43f03c507425248c44a913f --- dialin/dg/hd_proxy.py (.../hd_proxy.py) (revision 8ea13ae6dd10732bfcc456798f4785c4d88c95d3) +++ dialin/dg/hd_proxy.py (.../hd_proxy.py) (revision c864e886bc1b52d3a43f03c507425248c44a913f) @@ -5,7 +5,7 @@ # THIS CODE MAY NOT BE COPIED OR REPRODUCED IN ANY FORM, IN PART OR IN # WHOLE, WITHOUT THE EXPLICIT PERMISSION OF THE COPYRIGHT OWNER. # -# @file hd_proxy.py +# @file hd_simulator.py # # @author (last) Peter Lucia # @date (last) 26-Aug-2020 Index: dialin/ui/__init__.py =================================================================== diff -u -rf194e9e62ce2513823f5320774bc793448ee3809 -rc864e886bc1b52d3a43f03c507425248c44a913f --- dialin/ui/__init__.py (.../__init__.py) (revision f194e9e62ce2513823f5320774bc793448ee3809) +++ dialin/ui/__init__.py (.../__init__.py) (revision c864e886bc1b52d3a43f03c507425248c44a913f) @@ -1,2 +1,2 @@ -from .hd_proxy import HDSimulator -from .hd_proxy_alarms import HDProxyAlarms +from .hd_simulator import HDSimulator +from .hd_simulator_alarms import HDProxyAlarms Fisheye: Tag c864e886bc1b52d3a43f03c507425248c44a913f refers to a dead (removed) revision in file `dialin/ui/dg_proxy.py'. Fisheye: No comparison available. Pass `N' to diff? Fisheye: Tag 80501868a8f9a2c145945bbebb486fb3a32edfb2 refers to a dead (removed) revision in file `dialin/ui/dg_simulator.py'. Fisheye: No comparison available. Pass `N' to diff? Fisheye: Tag c864e886bc1b52d3a43f03c507425248c44a913f refers to a dead (removed) revision in file `dialin/ui/hd_proxy.py'. Fisheye: No comparison available. Pass `N' to diff? Fisheye: Tag c864e886bc1b52d3a43f03c507425248c44a913f refers to a dead (removed) revision in file `dialin/ui/hd_proxy_alarms.py'. Fisheye: No comparison available. Pass `N' to diff? Fisheye: Tag 80501868a8f9a2c145945bbebb486fb3a32edfb2 refers to a dead (removed) revision in file `dialin/ui/hd_simulator.py'. Fisheye: No comparison available. Pass `N' to diff? Index: dialin/ui/hd_simulator_alarms.py =================================================================== diff -u --- dialin/ui/hd_simulator_alarms.py (revision 0) +++ dialin/ui/hd_simulator_alarms.py (revision c864e886bc1b52d3a43f03c507425248c44a913f) @@ -0,0 +1,280 @@ +########################################################################### +# +# Copyright (c) 2019-2020 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 hd_simulator_alarms.py +# +# @author (last) Peter Lucia +# @date (last) 07-Aug-2020 +# @author (original) Peter Lucia +# @date (original) 07-Aug-2020 +# +############################################################################ +from ..protocols.CAN import (DenaliMessage, + DenaliCanMessenger, + DenaliChannels) +from logging import Logger +from ..utils.base import _AbstractSubSystem +from ..utils.conversions import integer_to_bytearray +from ..common.msg_defs import MsgIds +from ..common.alarm_defs import AlarmList, AlarmPriorities +from time import sleep, time + +HIGH = 3 +MED = 2 +LOW = 1 +NONE = 0 + + +class Alarms: + + # ALARM_ID = (priority, alarmID, escalates in, silent_espires_in, flags) + ALARM_ID_NO_ALARM = (NONE, 0, 0, 0, 0) + ALARM_ID_SOFTWARE_FAULT = (HIGH, 1, 0, 0, 0) + ALARM_ID_STUCK_BUTTON_TEST_FAILED = (HIGH, 2, 0, 0, 0) + ALARM_ID_FPGA_POST_TEST_FAILED = (HIGH, 3, 0, 0, 0) + ALARM_ID_WATCHDOG_POST_TEST_FAILED = (HIGH, 4, 0, 0, 0) + ALARM_ID_UI_COMM_POST_FAILED = (HIGH, 5, 0, 0, 0) + ALARM_ID_BLOOD_PUMP_MC_CURRENT_CHECK = (MED, 6, 0, 0, 0) + ALARM_ID_BLOOD_PUMP_OFF_CHECK = (MED, 7, 0, 0, 0) + ALARM_ID_BLOOD_PUMP_MC_DIRECTION_CHECK = (MED, 8, 0, 0, 0) + ALARM_ID_BLOOD_PUMP_ROTOR_SPEED_CHECK = (HIGH, 9, 0, 0, 0) + ALARM_ID_DIAL_IN_PUMP_MC_CURRENT_CHECK = (MED, 10, 0, 0, 0) + ALARM_ID_DIAL_IN_PUMP_OFF_CHECK = (MED, 11, 0, 0, 0) + ALARM_ID_DIAL_IN_PUMP_MC_DIRECTION_CHECK = (MED, 12, 0, 0, 0) + ALARM_ID_DIAL_IN_PUMP_ROTOR_SPEED_CHECK = (HIGH, 13, 0, 0, 0) + ALARM_ID_DIAL_OUT_PUMP_MC_CURRENT_CHECK = (MED, 14, 0, 0, 0) + ALARM_ID_DIAL_OUT_PUMP_OFF_CHECK = (MED, 15, 0, 0, 0) + ALARM_ID_DIAL_OUT_PUMP_MC_DIRECTION_CHECK = (MED, 16, 0, 0, 0) + ALARM_ID_DIAL_OUT_PUMP_ROTOR_SPEED_CHECK = (HIGH, 17, 0, 0, 0) + ALARM_ID_WATCHDOG_EXPIRED = (HIGH, 18, 0, 0, 0) + ALARM_ID_RTC_COMM_ERROR = (HIGH, 19, 0, 0, 0) + ALARM_ID_RTC_CONFIG_ERROR = (HIGH, 20, 0, 0, 0) + ALARM_ID_DG_COMM_TIMEOUT = (HIGH, 21, 0, 0, 0) + ALARM_ID_UI_COMM_TIMEOUT = (HIGH, 22, 0, 0, 0) + ALARM_ID_COMM_TOO_MANY_BAD_CRCS = (HIGH, 23, 0, 0, 0) + ALARM_ID_TREATMENT_STOPPED_BY_USER = (LOW, 24, 0, 0, 0) + ALARM_ID_BLOOD_SITTING_WARNING = (MED, 25, 0, 0, 0) + ALARM_ID_BLOOD_SITTING_TOO_LONG_NO_RESUME = (MED, 26, 0, 0, 0) + ALARM_ID_BLOOD_SITTING_TOO_LONG_NO_RINSEBACK = (HIGH, 27, 0, 0, 0) + ALARM_ID_CAN_MESSAGE_NOT_ACKED = (HIGH, 28, 0, 0, 0) + ALARM_ID_OCCLUSION_BLOOD_PUMP = (HIGH, 29, 0, 0, 0) + ALARM_ID_OCCLUSION_DIAL_IN_PUMP = (HIGH, 30, 0, 0, 0) + ALARM_ID_OCCLUSION_DIAL_OUT_PUMP = (HIGH, 31, 0, 0, 0) + ALARM_ID_ARTERIAL_PRESSURE_LOW = (HIGH, 32, 0, 0, 0) + ALARM_ID_ARTERIAL_PRESSURE_HIGH = (HIGH, 33, 0, 0, 0) + ALARM_ID_VENOUS_PRESSURE_LOW = (HIGH, 34, 0, 0, 0) + ALARM_ID_VENOUS_PRESSURE_HIGH = (HIGH, 35, 0, 0, 0) + ALARM_ID_UF_RATE_TOO_HIGH_ERROR = (HIGH, 36, 0, 0, 0) + ALARM_ID_UF_VOLUME_ACCURACY_ERROR = (HIGH, 37, 0, 0, 0) + ALARM_ID_RTC_BATTERY_LOW = (HIGH, 38, 0, 0, 0) + ALARM_ID_RTC_OR_TIMER_ACCURACY_FAILURE = (HIGH, 39, 0, 0, 0) + ALARM_ID_RTC_RAM_OPS_ERROR = (HIGH, 40, 0, 0, 0) + ALARM_ID_NVDATA_EEPROM_OPS_FAILURE = (HIGH, 41, 0, 0, 0) + ALARM_ID_NVDATA_MFG_RECORD_CRC_ERROR = (HIGH, 42, 0, 0, 0) + ALARM_ID_NVDATA_SRVC_RECORD_CRC_ERROR = (HIGH, 43, 0, 0, 0) + ALARM_ID_NVDATA_CAL_RECORD_CRC_ERROR = (HIGH, 44, 0, 0, 0) + ALARM_ID_NVDATA_HW_USAGE_DATA_CRC_ERROR = (HIGH, 45, 0, 0, 0) + ALARM_ID_NVDATA_DISINFECTION_DATE_CRC_ERROR = (HIGH, 46, 0, 0, 0) + ALARM_ID_RO_PUMP_OUT_PRESSURE_OUT_OF_RANGE = (HIGH, 47, 0, 0, 0) + ALARM_ID_TEMPERATURE_SENSORS_OUT_OF_RANGE = (HIGH, 48, 0, 0, 0) + ALARM_ID_TEMPERATURE_SENSORS_INCONSISTENT = (HIGH, 49, 0, 0, 0) + ALARM_ID_HD_COMM_TIMEOUT = (HIGH, 50, 0, 0, 0) + ALARM_ID_VALVE_CONTROL_FAILURE = (HIGH, 51, 0, 0, 0) + ALARM_ID_BLOOD_PUMP_FLOW_VS_MOTOR_SPEED_CHECK = (HIGH, 52, 0, 0, 0) + ALARM_ID_DIAL_IN_PUMP_FLOW_VS_MOTOR_SPEED_CHECK = (HIGH, 53, 0, 0, 0) + ALARM_ID_DIAL_OUT_PUMP_FLOW_VS_MOTOR_SPEED_CHECK = (HIGH, 54, 0, 0, 0) + ALARM_ID_BLOOD_PUMP_MOTOR_SPEED_CHECK = (HIGH, 55, 0, 0, 0) + ALARM_ID_DIAL_IN_PUMP_MOTOR_SPEED_CHECK = (HIGH, 56, 0, 0, 0) + ALARM_ID_DIAL_OUT_PUMP_MOTOR_SPEED_CHECK = (HIGH, 57, 0, 0, 0) + ALARM_ID_BLOOD_PUMP_ROTOR_SPEED_TOO_HIGH = (HIGH, 58, 0, 0, 0) + ALARM_ID_INLET_WATER_TEMPERATURE_OUT_OF_RANGE = (HIGH, 59, 0, 0, 0) + ALARM_ID_DOES_NOT_EXIST = (HIGH, 99, 0, 0, 0) + + +class HDProxyAlarms(_AbstractSubSystem): + def __init__(self, can_interface: DenaliCanMessenger, logger: Logger): + """ + @param can_interface: Denali Can Messenger object + """ + super().__init__() + self.can_interface = can_interface + self.logger = logger + self.flags = 0 + + if self.can_interface is not None: + channel_id = DenaliChannels.ui_to_hd_ch_id + self.can_interface.register_receiving_publication_function(channel_id, + MsgIds.MSG_ID_UI_ALARM_ACKNOWLEDGE.value, + self._handler_alarm_acknowledge) + self.can_interface.register_receiving_publication_function(channel_id, + MsgIds.MSG_ID_USER_REQUEST_ALARM_SILENCE.value, + self._handler_alarm_silence) + + def cmd_activate_alarm(self, alarm: AlarmList, + state: int = HIGH, + escalates_in: int = 0, + silence_expires: int = 0, + flags: int = 0): + """ + Activates the specified alarm + + + @param alarm: the alarm enum + @param state: Alarm priority + @param escalates_in: how long until the alarm escalates + @param silence_expires: seconds until silence expires + @param flags: additional alarm flags + Alarm flags: + eFlag_systemFault = 0 + eFlag_stop = 1 + eFlag_noClear = 2 + eFlag_noResume = 3 + eFlag_noRinseback = 4 + eFlag_noEndTreatment = 5 + eFlag_noNewTreatment = 6 + eFlag_bypassDialyzer = 7 + eFlag_alarmsToEscalate = 8 + eFlag_alarmsSilenced = 9 + eFlag_userAcknowledged = 10 + ... unused = 11 - 16 + @return: None + """ + + state = integer_to_bytearray(state) + top = integer_to_bytearray(alarm.value) + escalates_in = integer_to_bytearray(escalates_in) + silence_expires = integer_to_bytearray(silence_expires) + flags = integer_to_bytearray(flags) + + payload = state + top + escalates_in + silence_expires + flags + + message = DenaliMessage.build_message(channel_id=DenaliChannels.hd_alarm_broadcast_ch_id, + message_id=MsgIds.MSG_ID_ALARM_STATUS.value, + payload=payload) + + self.can_interface.send(message, 0) + + def cmd_make_alarm_flags(self, + system_fault=0, + stop=0, + no_clear=0, + no_resume=0, + no_rinseback=0, + no_end_treatment=0, + no_new_treatment=0, + bypass_dialyzer=0, + alarms_to_escalate=0, + alarms_silenced=0, + user_acknowledge=0, + ): + """ + Helper function to construct the flags + + @param system_fault: TBD + @param stop: TBD + @param no_clear: TBD + @param no_resume: TBD + @param no_rinseback: TBD + @param no_end_treatment: TBD + @param no_new_treatment: TBD + @param bypass_dialyzer: TBD + @param alarms_to_escalate: TBD + @param alarms_silenced: if the alarm should be silenced + @param user_acknowledge: if the user has already acknowledged the alarm + @return: (int) containing all the flags + """ + flags = 0 + flags ^= system_fault * 2 ** 0 \ + | stop * 2 ** 1 \ + | no_clear * 2 ** 2 \ + | no_resume * 2 ** 3 \ + | no_rinseback * 2 ** 4 \ + | no_end_treatment * 2 ** 5 \ + | no_new_treatment * 2 ** 6 \ + | bypass_dialyzer * 2 ** 7 \ + | alarms_to_escalate * 2 ** 8 \ + | alarms_silenced * 2 ** 9 \ + | user_acknowledge * 2 ** 10 + return flags + + def cmd_send_clear_alarms(self): + """ + Broadcasts a clear alarms message + + @return: None + """ + + state = integer_to_bytearray(AlarmList.ALARM_ID_NO_ALARM.value) + top = integer_to_bytearray(0) + escalates_in = integer_to_bytearray(0) + silence_expires = integer_to_bytearray(0) + flags = integer_to_bytearray(0) + + payload = state + top + escalates_in + silence_expires + flags + + message = DenaliMessage.build_message(channel_id=DenaliChannels.hd_alarm_broadcast_ch_id, + message_id=MsgIds.MSG_ID_ALARM_STATUS.value, + payload=payload) + + self.can_interface.send(message, 0) + + def cmd_repeat_broadcast_alarm(self, freq: int = 4, timeout: float = float('inf'), **kwargs): + """ + Broadcast the specified alarm message at particular frequency + + + @param freq: cycles / s of the broadcast + @param timeout: How long to broadcast the alarm for + @param kwargs: arguments to pass to cmd_activate_alarm + @return: None + """ + start = time() + current = time() + counter = 0 + silence_expires = 60 + + while current - start < timeout: + self.cmd_activate_alarm(flags=self.flags, silence_expires=silence_expires, **kwargs) + sleep(1.0 / freq) + current = time() + counter += 1 + if silence_expires > 0: + silence_expires -= 1 + else: + self.flags = self.cmd_make_alarm_flags(alarms_silenced=0) + + + def set_flags(self, flags): + """ + Sets the alarm flags + + @param flags: + @return: None + """ + self.flags = flags + + def _handler_alarm_acknowledge(self, message): + """ + Handles the alarm acknowledge message + + @param message: the message of user acknowledge, contains the alarmID + @return: None + """ + + self.logger.debug("Alarm acknowledged") + self.flags = self.flags | 2 ** 10 + + def _handler_alarm_silence(self, message): + """ + Handles the alarm silence message + + @param message: the message with 0 = cancel, 1 = silence + @return: None + """ + + self.logger.debug("Alarm Silence") + self.flags = self.flags | 2 ** 9 Index: tests/test_alarms.py =================================================================== diff -u -r39cffcc77c7c0ce645325cf50cad65874b2bb096 -rc864e886bc1b52d3a43f03c507425248c44a913f --- tests/test_alarms.py (.../test_alarms.py) (revision 39cffcc77c7c0ce645325cf50cad65874b2bb096) +++ tests/test_alarms.py (.../test_alarms.py) (revision c864e886bc1b52d3a43f03c507425248c44a913f) @@ -17,7 +17,9 @@ sys.path.append("..") from dialin.hd.hemodialysis_device import HD from dialin.utils.base import AbstractObserver -import time +from dialin.ui.hd_simulator import HDSimulator +from dialin.common.alarm_defs import AlarmList +from time import time, sleep class Observer(AbstractObserver): @@ -33,6 +35,7 @@ def check_something(self): pass + def test_disable_all_hd_alarms(): """ Disables all hd alarms @@ -42,12 +45,13 @@ if hd.cmd_log_in_to_hd(): hd.ui.cmd_ui_request_hd_version() - time.sleep(0.5) + sleep(0.5) print(f"Current HD version: {hd.ui.hd_version}") for desc, val in hd.alarms.ids.items(): hd.alarms.cmd_alarm_state_override(0, val) - time.sleep(0.5) + sleep(0.5) + def test_hd_alarms(): """ Simulates all HD alarms @@ -86,23 +90,44 @@ if hd.cmd_log_in_to_hd(): hd.ui.cmd_ui_request_hd_version() - time.sleep(0.5) + sleep(0.5) print(f"Current HD version: {hd.ui.hd_version}") for desc, val in hd.alarms.ids.items(): if desc in alarm_faults: print("Skipping {0}".format(desc)) continue print("Testing {0} = {1}".format(val.name, val.value)) success = hd.alarms.cmd_alarm_state_override(1, val.value) - time.sleep(3) + sleep(3) if not success: raise ValueError("Failed to activate alarm {0} = {1}".format(desc, val)) success = hd.alarms.cmd_alarm_state_override(0, val.value) if not success: raise ValueError("Failed to deactivate alarm {0} = {1}".format(desc, val)) - time.sleep(2) + sleep(2) +def test_clear_alarms(): + hd_sim = HDSimulator() + hd_sim.alarms_simulator.cmd_send_clear_alarms() + sleep(3) + hd_sim.alarms_simulator.cmd_activate_alarm(alarm=AlarmList.ALARM_ID_BLOOD_PUMP_MOTOR_SPEED_CHECK) + sleep(3) + hd_sim.alarms_simulator.cmd_send_clear_alarms() + sleep(1) + + +def test_repeated_alarm(): + hd_sim = HDSimulator(log_level="DEBUG") + hd_sim.alarms_simulator.cmd_send_clear_alarms() + sleep(2) + flags = hd_sim.alarms_simulator.cmd_make_alarm_flags(alarms_silenced=0, + user_acknowledge=0) + hd_sim.alarms_simulator.set_flags(flags) + hd_sim.alarms_simulator.cmd_repeat_broadcast_alarm(alarm=AlarmList.ALARM_ID_BLOOD_PUMP_MOTOR_SPEED_CHECK, + state=1, + timeout=20) + + if __name__ == '__main__': - # test_disable_all_hd_alarms() - test_hd_alarms() + test_repeated_alarm() Index: tests/test_hd_simulator.py =================================================================== diff -u -rc8bb93ad343033f2d0de5918c155a82281fe7102 -rc864e886bc1b52d3a43f03c507425248c44a913f --- tests/test_hd_simulator.py (.../test_hd_simulator.py) (revision c8bb93ad343033f2d0de5918c155a82281fe7102) +++ tests/test_hd_simulator.py (.../test_hd_simulator.py) (revision c864e886bc1b52d3a43f03c507425248c44a913f) @@ -17,11 +17,9 @@ import sys sys.path.append("..") -from dialin.ui.hd_proxy import HDSimulator, RequestRejectReasons -from dialin.ui.hd_proxy_alarms import Alarms +from dialin.ui.hd_simulator import HDSimulator, RequestRejectReasons from dialin.hd.hemodialysis_device import HD from dialin.utils.base import AbstractObserver -from dialin.squish.denaliMessages import clear_all_alarms from time import sleep @@ -77,18 +75,6 @@ sleep(1) -def test_clear_alarms(): - hd_simulator = HDSimulator() - - hd_simulator.alarms.cmd_activate_alarm(Alarms.ALARM_ID_COMM_TOO_MANY_BAD_CRCS) - sleep(3) - hd_simulator.alarms.cmd_send_clear_alarms() - sleep(3) - hd_simulator.alarms.cmd_activate_alarm(Alarms.ALARM_ID_COMM_TOO_MANY_BAD_CRCS) - sleep(3) - hd_simulator.alarms.cmd_send_clear_alarms() - # clear_all_alarms() - def test_valid_parameters(): hd_simulator = HDSimulator(log_level="DEBUG") @@ -114,6 +100,7 @@ ] hd_simulator.cmd_send_treatment_parameter_validation_response(rejections) + def test_invalid_parameters(): hd_simulator = HDSimulator(log_level="DEBUG") @@ -125,6 +112,7 @@ hd_simulator.cmd_send_treatment_parameter_manual_validation_response(rejections) sleep(2) + def test_priming(): hd_simulator = HDSimulator(log_level="DEBUG") state = 0 @@ -144,6 +132,7 @@ print(result) self.received_response = True + def test_start_confirm_end_treatment(): hd_simulator = HDSimulator(log_level="PRINT_ONLY") observer = StartTreatmentObserver() @@ -152,14 +141,9 @@ while not observer.received_response: sleep(0.50) + if __name__ == '__main__': - # sleep(1) - # test_poweroff() - # test_valid_parameters() - # test_priming() test_start_confirm_end_treatment() - # test_invalid_parameters() - # test_clear_alarms()