########################################################################### # # Copyright (c) 2020-2023 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.py # # @author (last) Darren Cox # @date (last) 08-Nov-2022 # @author (original) Peter Lucia # @date (original) 06-Aug-2020 # ############################################################################ import enum from typing import Callable from inspect import signature from . import messageBuilder from ..common import * from ..protocols.CAN import DenaliMessage, DenaliCanMessenger, DenaliChannels from ..utils import * from ..utils.base import AbstractSubSystem, LogManager from threading import Semaphore from random import random from copy import deepcopy from math import copysign import threading as threads_here from time import time, sleep def catch_exception(f): @functools.wraps(f) def func(*args, **kwargs): try: return f(*args, **kwargs) except Exception as e: print('Caught an exception in', f.__name__, e) return func class ErrorCatcher(type): def __new__(cls, name, bases, dct): for m in dct: if hasattr(dct[m], '__call__'): dct[m] = catch_exception(dct[m]) return type.__new__(cls, name, bases, dct) class DenaliData: # __metaclass__ = ErrorCatcher ## TBD! DEBUG only: remove MAX_NUM_PARAMETERS = int((DenaliMessage.MAX_NUMBER_OF_PAYLOAD_BYTES - DenaliMessage.PAYLOAD_START_INDEX) / 4) def __init__(self): self.data_num = 0 self.data_int = [None] * self.MAX_NUM_PARAMETERS self.data_float = [None] * self.MAX_NUM_PARAMETERS class HDSimulator(AbstractSubSystem): __metaclass__ = ErrorCatcher ## TBD! DEBUG only: remove NUM_TREATMENT_PARAMETERS = 19 instance_count = 0 MAX_UF_RATE_ML_PER_HR = 2500.0 MAX_UF_RATE_ML_PER_SEC = MAX_UF_RATE_ML_PER_HR / 3600 MAX_UF_ML_PER_TX = 7000.0 # UI version message field positions START_POS_MAJOR = DenaliMessage.PAYLOAD_START_INDEX END_POS_MAJOR = START_POS_MAJOR + 1 START_POS_MINOR = END_POS_MAJOR END_POS_MINOR = START_POS_MINOR + 1 START_POS_MICRO = END_POS_MINOR END_POS_MICRO = START_POS_MICRO + 1 START_POS_BUILD = END_POS_MICRO END_POS_BUILD = START_POS_BUILD + 2 START_POS_COMPAT = END_POS_BUILD END_POS_COMPAT = START_POS_COMPAT + 4 def __init__(self, can_interface: str = "can0", log_level: bool = None, console_out: bool = False, passive_mode: bool = True, auto_response: bool = False): """ The HDSimulator constructor @param can_interface: (str) the can interface name @param log_level: (str) or (None) if not set, contains the logging level @param console_out: (bool) If True, write each dialin message to the console. """ super().__init__() HDSimulator.instance_count = HDSimulator.instance_count + 1 self.demoCounter = 0 self.demoCount = 1 self.demoGroupCounter = 0 self.demoGroupCount = None self.demoSpeed = 1 # count multiplier self.demoSelection = 1 # current timer handling state self.demoTimedIncValue = None # value incremented on step of GroupCout, used in progress messages self.demoCountdownId = None # progress message ID to send while counting down self.demoCountdownChannel = None # used to fake as DG sometimes # default treatment settings, calculated parameters, accumulated status, and simulated data self.blood_flow_measured_ml_per_min = 188.8 # simulation self.blood_flow_set_point_ml_per_min = 190 # setting self.dialysate_flow_measured_ml_per_min = 229.9 # simulation self.dialysate_flow_set_point_ml_per_min = 250 # setting self.arterial_pressure_mmHg = -80.8 # simulation self.arterial_pressure_mid_mmHg = -118.9 # setting self.venous_pressure_mmHg = 203.3 # simulation self.venous_pressure_mid_mmHg = 165.2 # param self.treatment_time_sec = 60 * 100 # setting self.uf_volume_set_l = 1.2 # setting self.uf_volume_out_l = 0 # status self.uf_stop_time_sec = 60 * 2 # param self.uf_rate_l_per_sec = 0.001 # param self.salineVolume_ml = 0.0 # status self.salineVolCum_ml = 0.0 # status self.salineVolMax_ml = 800.0 # fixed R TBD! not enforced self.salineBolusRate_mlpersec = 150.0 / 60.0 # fixed R self.treatment_start_time_s = 0.0 # simulation R self.treatment_stop_time_s = 0.0 # simulation R self.blood_stop_time_s = 0.0 # simulation R self.rinseback_volume_set_ml = 80 + 120 # setting self.rinseback_volume_out_ml = 0 # status self.rinseback_volume_all_ml = 0 # status self.rinsebackVelocity_ml_per_sec = 100.0 / 60.0 # param self.rinsebackVelocity_max_ml_per_sec = 150.0 / 60.0 # fixed R self.rinsebackVelocity_min_ml_per_sec = 50.0 / 60.0 # fixed R self.heparin_rate_ml_per_hr = 0 # setting self.heparin_bolus_ml = 0 # setting self.heparin_stop_min = 0 # setting self.heparin_out_ml = 0 # status self.heparin_pause = False # setting self.heparin_state = HeparinStates.HEPARIN_STATE_STOPPED # status self.saline_state = SalineBolusStates.SALINE_BOLUS_STATE_IDLE.value # status self.uf_state = TreatmentStates.TREATMENT_DIALYSIS_STATE.value # status self.demo_treatment_params = DenaliData() self.demoSemaphore = Semaphore(1) # keep message and timer handling separate self.auto_response = auto_response self._log_manager = LogManager(log_level=log_level, log_filepath=self.__class__.__name__ + ".log") self.logger = self._log_manager.logger self.console_out = console_out self.can_interface = DenaliCanMessenger(can_interface=can_interface, logger=self.logger, log_can=self._log_manager.log_level == "CAN_ONLY", console_out=console_out, passive_mode=passive_mode) self.can_interface.start() if self.can_interface is not None: channel_id = DenaliChannels.ui_to_hd_ch_id # define use of a generic handler as a default self.can_interface.register_receiving_publication_function(channel_id, None, # MSG_ID_UNUSED, self._handler_ui_generic) else: self.logger.debug("can_interface not init ") self.treatment_parameter_rejections = TreatmentParameterRejections() # initialize variables that will be populated by UI version response self.ui_version = None # start timer handler self.demoTimer = threads_here.Timer(1.0, self.run_demoTimer_timeout) self.demoTimer.start() def set_ui_all_publication(self, function_ptr: Callable) -> None: """ Allows later addition of publication to the HDSimulator This function needs improvements, it has been implemented to quickly being used by Development Testing team. @param function_ptr: (Callable) the pointer to the message handler function @return: None """ function_signature_exp = "(message:dict)->None" # TODO: update later to get param name and type. if not callable(function_ptr): print("ui all publication rejected (not a function)") self.logger.debug("ui all publication rejected (not a function)") else: function_signature_act = str(signature(function_ptr)).replace(" ", "") if function_signature_act == function_signature_exp: self.can_interface.register_received_all_ui_publication_function(function_ptr) else: print("ui all publication rejected {0},{1}" .format(function_signature_exp, function_signature_act)) self.logger.debug( "rejected ui all messages publication registration, expected function signature {0}, got {1} " .format(function_signature_exp, function_signature_act)) def get_ui_version(self): """ Gets the ui version @return: The ui version """ return self.ui_version def _handler_system_usage_response(self) -> None: """ Handles a request for system usage @return: None """ payload = integer_to_bytearray(1619628663) payload += integer_to_bytearray(1619887863) message = DenaliMessage.build_message(channel_id=DenaliChannels.hd_to_ui_ch_id, message_id=MsgIds.MSG_ID_HD_SERVICE_SCHEDULE_DATA.value, payload=payload) self.can_interface.send(message, 0) @ staticmethod def get_message_list_data(message: dict) -> DenaliData: ret_data = DenaliData() offset = MsgFieldPositions.START_POS_FIELD_1 m_num_p = min(message['message'][DenaliMessage.PAYLOAD_LENGTH_INDEX], int( (len(message['message']) - offset) / 4 )) ret_data.data_num = m_num_p ret_data.data_int[0:m_num_p] = struct.unpack('{}i'.format(m_num_p), bytearray(message['message'][offset:offset+m_num_p*4])) ret_data.data_float[0:m_num_p] = struct.unpack('{}f'.format(m_num_p), bytearray(message['message'][offset:offset+m_num_p*4])) return ret_data ''' TBD! @ staticmethod def reinterpet_int_as_float(integer: int) -> float: return struct.unpack('!f', struct.pack('!i', integer))[0] @ staticmethod def reinterpet_float_as_int(float_input: float) -> int: return struct.unpack('!i', struct.pack('!f', float))[0] ''' def _handler_ui_generic(self, message: dict) -> None: """ DRY-DEMO: Handles a generic UI request to HD @param message: (dict) the message containing the request @return: None """ self.demoSemaphore.acquire() dialin_msg_id = DenaliMessage.get_message_id(message) ignore_typical_msg_ids = { MsgIds.MSG_ID_UI_CHECK_IN.value, MsgIds.MSG_ID_ACK_MESSAGE_THAT_REQUIRES_ACK.value } msg_id = DenaliMessage.get_message_id(message) rsp_id = None rsp_payload = None if msg_id in ignore_typical_msg_ids: print(".", end='') else: print("generic handler:", MsgIds(msg_id).name, message) params = self.get_message_list_data(message) # convert payload data # UI RESET -------------------------------------------------------- if msg_id == MsgIds.MSG_ID_UI_POST_FINAL_TEST_RESULT.value: self.demoSelection = 1 # setup event to change Mode self.demoCount = 0 self.demoCounter = 1 # RESET/JUMP FORWARD DIALOG HANDLER ------------------------------- if (msg_id == MsgIds.MSG_ID_UI_CONFIRMATION_RESULT_RESPONSE.value) and \ (params.data_int[0] == 99) and \ (params.data_int[1] == EResponse.Accepted): # TBD! Use const for 99 rsp_id = MsgIds.MSG_ID_HD_UI_CONFIRMATION_REQUEST rsp_payload = integer_to_bytearray(98) rsp_payload += integer_to_bytearray(0) # TBD use consts rsp_payload += integer_to_bytearray(0) self.pause_demoSelection = self.demoSelection # setup event to change state on demoTimer self.pause_demoCount = self.demoCount self.pause_demoId = self.demoCountdownId self.demoGroupCounter += self.demoCounter self.demoSelection = 0 # setup event to change state on demoTimer self.demoCount = 0 elif (msg_id == MsgIds.MSG_ID_UI_CONFIRMATION_RESULT_RESPONSE.value) and \ (params.data_int[0] == 98): rsp_id = MsgIds.MSG_ID_HD_UI_CONFIRMATION_REQUEST rsp_payload = integer_to_bytearray(98) # TBD! Use const for 99 rsp_payload += integer_to_bytearray(3) # TBD use consts rsp_payload += integer_to_bytearray(0) if params.data_int[1] == EResponse.Accepted: # Reset the Demo self.demoSelection = 1 # setup event to change Mode self.demoCount = 0 self.demoCounter = 1 else: # EResponse.Rejected - Just Jump Forward on any current counter self.demoSelection = self.pause_demoSelection # setup event to change state on demoTimer self.demoCountdownId = self.pause_demoId self.demoCount = self.pause_demoCount self.demoCounter = self.demoCount - 1 if self.demoSelection == 42: # Rinseback in process; short cycle it self.rinseback_volume_out_ml = self.rinseback_volume_set_ml - 1 self.rinseback_volume_all_ml = self.rinseback_volume_out_ml # INITIATE A NEW TREATMENT SETUP ---------------------------------- elif msg_id == MsgIds.MSG_ID_UI_INITIATE_TREATMENT_REQUEST.value: rsp_id = MsgIds.MSG_ID_HD_OP_MODE_DATA if params.data_int[0] == EResponse.Accepted: rsp_payload = integer_to_bytearray(HDOpModes.MODE_TPAR.value) else: rsp_payload = integer_to_bytearray(HDOpModes.MODE_STAN.value) rsp_payload += integer_to_bytearray(0) elif msg_id == MsgIds.MSG_ID_UI_NEW_TREATMENT_PARAMS_REQUEST.value: rsp_id = MsgIds.MSG_ID_HD_NEW_TREATMENT_PARAMS_RESPONSE rsp_payload = struct.pack("<" + str(TreatmentParameters.NUM_OF_TREATMENT_PARAMS.value + 1) + "i", 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 ) self.demo_treatment_params = deepcopy(params) # keep a complete copy # UF Treatment self.treatment_time_sec = params.data_int[TreatmentParameters.TREATMENT_PARAM_TREATMENT_DURATION_MIN.value] * 60 self.uf_volume_set_l = 0 # TBD not in this message self.uf_volume_out_l = 0 # reset status self.uf_rate_l_per_sec = 0 self.salineVolume_ml = 0.0 # status self.salineVolCum_ml = 0.0 # status self.dialysate_flow_set_point_ml_per_min = params.data_int[TreatmentParameters.TREATMENT_PARAM_DIALYSATE_FLOW_RATE_ML_MIN.value] self.blood_flow_set_point_ml_per_min = params.data_int[TreatmentParameters.TREATMENT_PARAM_BLOOD_FLOW_RATE_ML_MIN.value] # rsp_id = MsgIds.MSG_ID_HD_PRESSURE_LIMITS_CHANGE_RESPONSE self.cmd_send_treatment_adjust_pressures_limit_response( accepted=EResponse.Accepted, reason=RequestRejectReasons.REQUEST_REJECT_REASON_NONE.value, arterial_pressure_limit_window=self.demo_treatment_params.data_int[TreatmentParameters.TREATMENT_PARAM_ART_PRES_LIMIT_WINDOW.value], venous_pressure_limit_window=self.demo_treatment_params.data_int[TreatmentParameters.TREATMENT_PARAM_VEN_PRES_LIMIT_WINDOW.value], venous_pressure_limit_asymmetric=self.demo_treatment_params.data_int[TreatmentParameters.TREATMENT_PARAM_VEN_PRES_LIMIT_ASYMMETRIC.value] ) self.arterial_pressure_mid_mmHg = (self.demo_treatment_params.data_int[TreatmentParameters.TREATMENT_PARAM_ARTERIAL_PRESSURE_LOW_LIMIT_MMHG.value] + self.demo_treatment_params.data_int[TreatmentParameters.TREATMENT_PARAM_ARTERIAL_PRESSURE_HIGH_LIMIT_MMHG.value]) / 2 self.venous_pressure_mid_mmHg = (self.demo_treatment_params.data_int[TreatmentParameters.TREATMENT_PARAM_VENOUS_PRESSURE_LOW_LIMIT_MMHG.value] + self.demo_treatment_params.data_int[TreatmentParameters.TREATMENT_PARAM_VENOUS_PRESSURE_HIGH_LIMIT_MMHG.value]) / 2 self.arterial_pressure_mmHg = self.arterial_pressure_mid_mmHg - 32.0 # simulation self.venous_pressure_mmHg = self.venous_pressure_mid_mmHg + 32 # simulation # Rinseback self.rinseback_volume_set_ml = 80 + 120 # TBD! 80 + should be based on dialyzer blood volume? self.rinsebackVelocity_ml_per_sec = params.data_int[TreatmentParameters.TREATMENT_PARAM_RINSEBACK_FLOW_RATE_ML_MIN.value] / 60.0 self.rinseback_volume_out_ml = 0 # reset status self.rinseback_volume_all_ml = 0 # Heparin self.heparin_stop_min = params.data_int[TreatmentParameters.TREATMENT_PARAM_HEPARIN_PRESTOP_MIN.value] self.heparin_rate_ml_per_hr = params.data_float[TreatmentParameters.TREATMENT_PARAM_HEPARIN_DISPENSE_RATE_ML_HR.value] self.heparin_rate_ml_per_s = self.heparin_rate_ml_per_hr / 3600.0 self.heparin_bolus_ml = params.data_float[TreatmentParameters.TREATMENT_PARAM_HEPARIN_BOLUS_VOLUME_ML.value] self.heparin_out_ml = 0 # reset status self.heparin_pause = False # setting self.heparin_expected_ml = ((self.treatment_time_sec / 60.0) - self.heparin_stop_min) / 60.0 * self.heparin_rate_ml_per_hr + self.heparin_bolus_ml # 'Secret' speed setting: 10x heparin bolus volume, when heparinStopTime=430 and heparinRate is OFF # if math.isclose(sto, 430) and math.isclose(hdr, 0) and (hbv > 0.199): # self.demoSpeed = int(hbv * 10 + 0.5) # else: # self.demoSpeed = 1 elif msg_id == MsgIds.MSG_ID_UI_USER_CONFIRM_TREATMENT_PARAMS_REQUEST.value: if params.data_int[0] == EResponse.Accepted: ## TBD!!! FOLLOWING IS FUTILE ATTEMPT TO SET UF MAX calc_max_uf_volume = min(int(self.MAX_UF_RATE_ML_PER_SEC * self.treatment_time_sec/100.0)*100.0, self.MAX_UF_ML_PER_TX) self.cmd_set_treatment_parameter_ranges(min_treatment_duration=0, max_treatment_duration=480, min_uf_volume=0.0, max_uf_volume=calc_max_uf_volume, min_dialysate_flow_rate=100, max_dialysate_flow_rate=600) ## TBD!!! Does this work? self.cmd_send_uf_treatment_response(accepted=EResponse.Accepted, reason=RequestRejectReasons.REQUEST_REJECT_REASON_NONE.value, volume=calc_max_uf_volume) ## TBD!! Is this needed to clear UI from presenting old data? self.cmd_set_treatment_saline_bolus_data(target=self.demo_treatment_params.data_int[TreatmentParameters.TREATMENT_PARAM_SALINE_BOLUS_VOLUME_ML.value], cumulative=0.0, delivered=0.0) self.salineVolume_ml = 0.0 self.salineVolCum_ml = 0.0 rsp_id = MsgIds.MSG_ID_PRE_TREATMENT_STATE_DATA rsp_payload = struct.pack( "<" + str(PreTreatmentSubModes.NUM_OF_HD_PRE_TREATMENT_STATES.value + 1) + "i", PreTreatmentSubModes.HD_PRE_TREATMENT_WATER_SAMPLE_STATE.value, PreTreatmentSampleWaterStates.SAMPLE_WATER_SETUP_STATE.value, # 0 Water sample state 0, # 1 Consumable self-tests state 0, # 2 No cartridge self-tests state 0, # 3 Consumable and cartridge installation state 0, # 4 Self-tests when the cartridge is dry state 0, # 5 Prime blood and dialysate circuits and run wet self-tests state 0, # 6 Re-circulate blood and dialysate circuits state 0, # 7 Patient connection state 0, 0 #TBD! late add ) self.demoSelection = 4 # TBD setup event to change to Filter Flush self.demoCount = 0 # immediately act on next timer tick self.demoCounter = 0 else: # elif request_val == EResponse.Rejected: rsp_id = MsgIds.MSG_ID_HD_NEW_TREATMENT_PARAMS_RESPONSE rsp_payload = integer_to_bytearray(EResponse.Rejected) rsp_payload += integer_to_bytearray(0) # WATER SAMPLING -------------------------------------------------- elif msg_id == MsgIds.MSG_ID_UI_SAMPLE_WATER_RESULT.value: # sample water completed, prime filter... if params.data_int[0] == EResponse.Accepted: self.demoTimedIncValue = PreTreatmentConsumableSelfTestStates.CONSUMABLE_SELF_TESTS_INSTALL_STATE.value rsp_id = MsgIds.MSG_ID_PRE_TREATMENT_STATE_DATA rsp_payload = struct.pack( "<" + str(PreTreatmentSubModes.NUM_OF_HD_PRE_TREATMENT_STATES.value + 1) + "i", PreTreatmentSubModes.HD_PRE_TREATMENT_SELF_TEST_CONSUMABLE_STATE.value, 0, # 0 Water sample state self.demoTimedIncValue, # 1 Consumable self-tests state 0, # 2 No cartridge self-tests state 0, # 3 Consumable and cartridge installation state 0, # 4 Self-tests when the cartridge is dry state 0, # 5 Prime blood and dialysate circuits and run wet self-tests state 0, # 6 Re-circulate blood and dialysate circuits state 0, # 7 Patient connection state 0, 0 #TBD! late add ) self.demoSelection = 0 # Wait for user self.demoCount = 3 # TBD! setup quick action: 3 timer ticks each step self.demoCounter = 0 else: # elif request_val == EResponse.Rejected: # 'Secret' jump over pre-treatment when user fails the water test rsp_id = MsgIds.MSG_ID_PRE_TREATMENT_STATE_DATA rsp_payload = struct.pack( "<" + str(PreTreatmentSubModes.NUM_OF_HD_PRE_TREATMENT_STATES.value + 1) + "i", PreTreatmentSubModes.HD_PRE_TREATMENT_PATIENT_CONNECTION_STATE.value, 0, # 0 Water sample state 0, # 1 Consumable self-tests state 0, # 2 No cartridge self-tests state 0, # 3 Consumable and cartridge installation state 0, # 4 Self-tests when the cartridge is dry state 0, # 5 Prime blood and dialysate circuits and run wet self-tests state 0, # 6 Re-circulate blood and dialysate circuits state 0, # 7 TBD! Patient connection state 0, 0 # TBD! late add ) self.demoSelection = 0 # Wait for user # PRETREATMENT INSTALL AND TESTS --------------------------------- elif msg_id == MsgIds.MSG_ID_UI_CONSUMABLE_INSTALL_CONFIRM_REQUEST.value: # assuming everything is setup in preceding MSG_ID_UI_SAMPLE_WATER_RESULT handling self.demoSelection = 6 # TBD setup event to change to self.demoCounter = self.demoCount # next tick self.demoCount = 3 # TBD redundant with above? self.demoCounter = self.demoCount # immediate self.demoTimedIncValue = PreTreatmentConsumableSelfTestStates.CONSUMABLE_SELF_TESTS_PRIME_STATE.value # 1. TBD Note: this will be skipped as an output elif msg_id == MsgIds.MSG_ID_UI_INSTALLATION_CONFIRM_REQUEST.value: self.demoTimedIncValue = PreTreatmentDrySelfTestsStates.DRY_SELF_TESTS_START_STATE.value self.demoSelection = 9 # TBD setup event to change to test completions self.demoCount = 4 # 4 timer ticks each for 12-16 TBD?? self.demoCounter = self.demoCount self.demoCountdownId = MsgIds.MSG_ID_HD_DRY_SELF_TEST_PROGRESS_DATA # aggregate # PreTreatmentDrySelfTestsStates.NUM_OF_DRY_SELF_TESTS_STATES * 2 self.demoGroupCounter = -self.demoCounter # start displayed timer progressing with the Counter self.demoGroupCount = PreTreatmentDrySelfTestsStates.NUM_OF_DRY_SELF_TESTS_STATES.value * self.demoCount # aggregate counter TBD! calc the 13 elif msg_id == MsgIds.MSG_ID_UI_START_PRIME_REQUEST.value: # _60 x3C 'Start Prime' self.demoSelection = 10 # continue on, picking up from previous state setup by selection 9 self.demoCount = 5 self.demoCounter = self.demoCount # TBD! creates illusion of starting self.demoGroupCount = PreTreatmentPrimeStates.NUM_OF_HD_PRIME_STATES.value * self.demoCount # aggregate counter self.demoGroupCounter = 0 # aggregate counter TBD SHOULD be handled by previous group end logic (?) # TREATMENT: PATIENT CONNECT -------------------------------------- elif msg_id == MsgIds.MSG_ID_UI_PATIENT_CONNECTION_BEGIN_REQUEST.value: # 100 x64 'Continue' Priming Complete # TBD!!! Setup UF Range before this rsp_id = MsgIds.MSG_ID_PRE_TREATMENT_STATE_DATA rsp_payload = struct.pack( "<" + str(PreTreatmentSubModes.NUM_OF_HD_PRE_TREATMENT_STATES.value + 1) + "i", PreTreatmentSubModes.HD_PRE_TREATMENT_PATIENT_CONNECTION_STATE.value, 0, # 0 Water sample state 0, # 1 Consumable self-tests state 0, # 2 No cartridge self-tests state 0, # 3 Consumable and cartridge installation state 0, # 4 Self-tests when the cartridge is dry state 0, # 5 Prime blood and dialysate circuits and run wet self-tests state 0, # 6 Re-circulate blood and dialysate circuits state 0, # 7 TBD! Patient connection state 0, 0 # TBD! late add ) self.demoSelection = 0 # Wait for user elif msg_id == MsgIds.MSG_ID_UI_SET_UF_VOLUME_PARAMETER_REQUEST.value: # _79 x4F rsp_id = MsgIds.MSG_ID_HD_SET_UF_VOLUME_PARAMETER_RESPONSE rsp_payload = integer_to_bytearray(EResponse.Accepted) rsp_payload += integer_to_bytearray(RequestRejectReasons.REQUEST_REJECT_REASON_NONE.value) rsp_payload += integer_to_bytearray(params.data_int[0]) # echo back setting self.uf_volume_set_l = params.data_float[0] / 1000.0 # TBD!! passed as mL instead if (self.uf_volume_set_l is not None) and (self.uf_volume_set_l >= 0.0009 ) and (self.uf_stop_time_sec is not None): self.uf_rate_l_per_sec = self.uf_volume_set_l / (self.treatment_time_sec - self.uf_stop_time_sec) # param else: self.uf_rate_l_per_sec = 0 self.demoSelection = 0 # Wait for user elif msg_id == MsgIds.MSG_ID_UI_PATIENT_CONNECTION_CONFIRM_REQUEST.value: # 102 x66 rsp_id = MsgIds.MSG_ID_HD_PATIENT_CONNECTION_CONFIRM_RESPONSE rsp_payload = integer_to_bytearray(EResponse.Accepted) rsp_payload += integer_to_bytearray(RequestRejectReasons.REQUEST_REJECT_REASON_NONE.value) self.demoSelection = 0 # Wait for user elif msg_id == MsgIds.MSG_ID_UI_START_TREATMENT_REQUEST.value: # 113 x71 rsp_id = MsgIds.MSG_ID_HD_START_TREATMENT_RESPONSE rsp_payload = integer_to_bytearray(EResponse.Accepted) rsp_payload += integer_to_bytearray(RequestRejectReasons.REQUEST_REJECT_REASON_NONE.value) self.demoSelection = 20 # Start Treatment self.demoCount = 0 # immediately # DIALYSIS --------------------------------------------------- elif msg_id == MsgIds.MSG_ID_USER_TREATMENT_TIME_CHANGE_REQUEST.value: # _22 x16 rsp_id = MsgIds.MSG_ID_USER_TREATMENT_TIME_CHANGE_RESPONSE if params.data_int[0] is not None: if params.data_int[0] == 4 * 60 + 30: # 'secret' means to force end if 0 duration is unavailable params.data_int[0] = 0 self.treatment_time_sec = params.data_int[0] * 60 rsp_payload = integer_to_bytearray(EResponse.Accepted) rsp_payload += integer_to_bytearray(RequestRejectReasons.REQUEST_REJECT_REASON_NONE.value) else: rsp_payload = integer_to_bytearray(EResponse.Rejected) rsp_payload += integer_to_bytearray(RequestRejectReasons.REQUEST_REJECT_REASON_TREATMENT_TIME_OUT_OF_RANGE.value) rsp_payload += integer_to_bytearray(int(self.treatment_time_sec / 60)) rsp_payload += float_to_bytearray(self.uf_volume_set_l * 1000.0) elif msg_id == MsgIds.MSG_ID_USER_SALINE_BOLUS_REQUEST.value: # _18 x12 rsp_id = MsgIds.MSG_ID_USER_SALINE_BOLUS_RESPONSE rsp_payload = integer_to_bytearray(EResponse.Accepted) rsp_payload += integer_to_bytearray(RequestRejectReasons.REQUEST_REJECT_REASON_NONE.value) if params.data_int[0] == EResponse.Accepted: self.demoSelection = 31 # Saline Bolus else: self.demoSelection = 33 # Bolus Stop request elif msg_id == MsgIds.MSG_ID_UI_HEPARIN_PAUSE_RESUME_REQUEST.value: # _.. x4B rsp_id = MsgIds.MSG_ID_HD_HEPARIN_PAUSE_RESUME_RESPONSE rsp_payload = integer_to_bytearray(EResponse.Accepted) rsp_payload += integer_to_bytearray(RequestRejectReasons.REQUEST_REJECT_REASON_NONE.value) if params.data_int[0] != EResponse.Accepted: self.heparin_pause = True # Heparin Pause else: self.heparin_pause = False # Heparin Resume elif msg_id == MsgIds.MSG_ID_UI_PRESSURE_LIMITS_CHANGE_REQUEST.value: # _70 x46 rsp_id = MsgIds.MSG_ID_HD_PRESSURE_LIMITS_CHANGE_RESPONSE rsp_payload = integer_to_bytearray(EResponse.Accepted) rsp_payload += integer_to_bytearray(RequestRejectReasons.REQUEST_REJECT_REASON_NONE.value) # apply new settings self.demo_treatment_params.data_int[TreatmentParameters.TREATMENT_PARAM_ART_PRES_LIMIT_WINDOW.value] =\ params.data_int[0] self.demo_treatment_params.data_int[TreatmentParameters.TREATMENT_PARAM_VEN_PRES_LIMIT_WINDOW.value] =\ params.data_int[1] self.demo_treatment_params.data_int[TreatmentParameters.TREATMENT_PARAM_VEN_PRES_LIMIT_ASYMMETRIC.value] =\ params.data_int[2] rsp_payload += integer_to_bytearray(params.data_int[0]) # reply with as set rsp_payload += integer_to_bytearray(params.data_int[1]) rsp_payload += integer_to_bytearray(params.data_int[2]) elif msg_id == MsgIds.MSG_ID_USER_BLOOD_DIAL_RATE_CHANGE_REQUEST.value: # _70 x46 rsp_id = MsgIds.MSG_ID_USER_BLOOD_DIAL_RATE_CHANGE_RESPONSE rsp_payload = integer_to_bytearray(EResponse.Accepted) rsp_payload += integer_to_bytearray(RequestRejectReasons.REQUEST_REJECT_REASON_NONE.value) # apply new settings self.demo_treatment_params.data_int[TreatmentParameters.TREATMENT_PARAM_BLOOD_FLOW_RATE_ML_MIN.value] =\ params.data_int[0] self.demo_treatment_params.data_int[TreatmentParameters.TREATMENT_PARAM_DIALYSATE_FLOW_RATE_ML_MIN.value] =\ params.data_int[1] self.dialysate_flow_set_point_ml_per_min = params.data_int[1] self.blood_flow_set_point_ml_per_min = params.data_int[0] rsp_payload += integer_to_bytearray(params.data_int[1]) # reply with as set rsp_payload += integer_to_bytearray(params.data_int[0]) # RINSEBACK ------------------------- elif msg_id == MsgIds.MSG_ID_UI_RINSEBACK_CMD_REQUEST.value: # _82 x52 rsp_id = MsgIds.MSG_ID_HD_RINSEBACK_CMD_RESPONSE rsp_payload = integer_to_bytearray(EResponse.Accepted) rsp_payload += integer_to_bytearray(RequestRejectReasons.REQUEST_REJECT_REASON_NONE.value) if params.data_int[0] ==0: # 0 Start Rinseback self.rinseback_volume_out_ml = 0 # reset these self.demoSelection = 41 elif params.data_int[0] == 1: # 1 Accelerate ++25 mL/min (R) self.rinsebackVelocity_ml_per_sec = min( self.rinsebackVelocity_ml_per_sec + 25.0 / 60.0, self.rinsebackVelocity_max_ml_per_sec ) elif params.data_int[0] == 2: # 2 Decelerate --25 mL/min (R) self.rinsebackVelocity_ml_per_sec = max( self.rinsebackVelocity_ml_per_sec - 25.0 / 60.0, self.rinsebackVelocity_min_ml_per_sec ) elif params.data_int[0] == 3: # 3 Pause self.demoSelection = 44 elif params.data_int[0] == 4: # 4 Resume self.demoSelection = 41 elif params.data_int[0] == 6: # TBD!! Additional # TreatmentRinsebackStates.RINSEBACK_RUN_ADDITIONAL_STATE.value #TBD!! # 4 Additional rinseback volume (10 mL) state of the rinseback sub-mode state machine self.rinseback_volume_set_ml += 10.0 # ++10 mL (R) self.demoSelection = 45 elif params.data_int[0] == 5 or\ params.data_int[0] == 8: # TBD!! need real ID consts: "END" 8 = Disconnect without further rinseback 5 = "End" during rinseback rsp_id = MsgIds.MSG_ID_HD_OP_MODE_DATA rsp_payload = integer_to_bytearray(HDOpModes.MODE_POST.value) rsp_payload += integer_to_bytearray(0) self.demoSelection = 0 # Complete Treatment self.demoCount = 0 # immediately # rsp_id = MsgIds.MSG_ID_HD_TREATMENT_LOG_DATA_RESPONSE self._send_treatment_log() # TBD! pre-sending prior to final transition to better assure receipt(?) elif params.data_int[0] == 9: # TBD!! Additional not implemented 9 = return to treatment rsp_payload = integer_to_bytearray(EResponse.Rejected) elif msg_id == MsgIds.MSG_ID_UI_PATIENT_DISCONNECTION_CONFIRM_REQUEST.value or \ msg_id == MsgIds.MSG_ID_UI_TREATMENT_LOG_DATA_REQUEST.value: # _74 x4A # rsp_id = MsgIds.MSG_ID_HD_TREATMENT_LOG_DATA_RESPONSE self._send_treatment_log() elif msg_id == MsgIds.MSG_ID_UI_DISPOSABLE_REMOVAL_CONFIRM_REQUEST.value: # 115 x73 rsp_id = MsgIds.MSG_ID_HD_DISPOSABLE_REMOVAL_CONFIRM_RESPONSE rsp_payload = integer_to_bytearray(EResponse.Accepted) rsp_payload += integer_to_bytearray(RequestRejectReasons.REQUEST_REJECT_REASON_NONE.value) self.demoSelection = 1 # Reset to beginning (rather than Disinfect choices '= 50') self.demoCount = 0 # immediately # # -------------------------------------------------- # Send Response if needed if rsp_id != None: rsp_message = DenaliMessage.build_message(channel_id=DenaliChannels.hd_to_ui_ch_id, message_id=rsp_id.value, payload=rsp_payload) print("\nR send message:", MsgIds(rsp_id).name, rsp_message) self.can_interface.send(rsp_message, 0) self.demoSemaphore.release() def run_demoTimer_timeout(self) -> None: start_time = time() while self.demoTimer.is_alive(): self.do_demoTimer_timeout() elapsed_time = (time()-start_time) wait_time = 1.0 - (elapsed_time % 1.0) sleep(wait_time) self.demoTimer.cancel() def do_demoTimer_timeout(self) -> None: """ DRY-DEMO: one second timer to advance demo events :return: None """ self.demoSemaphore.acquire() rsp_id = None rsp_payload = None rsp_channel_id = DenaliChannels.hd_to_ui_ch_id # default if (self.demoCounter <= self.demoCount) or (self.demoCount < 1): ctrIncrement = max(1, min(self.demoCount - self.demoCounter, self.demoSpeed)) self.demoCounter += ctrIncrement if self.demoGroupCount is not None: self.demoGroupCounter += ctrIncrement if self.demoCounter <= self.demoCount: print("t", end='') # TBD else: print("T", end='') # TBD self.demoCounter = 0 self.demoCountdownId = None self.demoCountdownChannel = None if self.demoSelection != 0: # TBD if self.demoTimedIncValue is not None: self.demoTimedIncValue += 1 # RESET INIT TO STANDBY ------------------------------------------- if self.demoSelection == 1: # TBD Reset to start INIT rsp_id = MsgIds.MSG_ID_HD_OP_MODE_DATA rsp_payload = integer_to_bytearray(HDOpModes.MODE_INIT.value) rsp_payload += integer_to_bytearray(HDInitStates.POST_STATE_START.value) self.demoSelection = 2 # setup event to change state on demoTimer self.demoCount = 3 self.demoGroupCount = None elif self.demoSelection == 2: # TBD Auto transit to standby rsp_id = MsgIds.MSG_ID_HD_OP_MODE_DATA rsp_payload = integer_to_bytearray(HDOpModes.MODE_STAN.value) rsp_payload += integer_to_bytearray(HDStandbyStates.STANDBY_START_STATE.value) self.demoSelection = 0 # wait for user # PRETREATMENT ---------------------------------------------------- elif self.demoSelection == 4: # = PreTreatmentConsumableSelfTestStates.CONSUMABLE_SELF_TESTS_COMPLETE_STATE.value: self.demoSelection = 7 # TBD! setup event to change to else: pass # repeat this selection elif self.demoSelection == 7: # No Cartridge Self Tests rsp_id = MsgIds.MSG_ID_PRE_TREATMENT_STATE_DATA rsp_payload = struct.pack( "<" + str(PreTreatmentSubModes.NUM_OF_HD_PRE_TREATMENT_STATES.value + 1) + "i", PreTreatmentSubModes.HD_PRE_TREATMENT_SELF_TEST_NO_CART_STATE.value, 0, # 0 Water sample state 0, # 1 Consumable self-tests state PreTreatmentNoCartSelfTestStates.NO_CART_SELF_TESTS_PRESSURE_CHECKS_STATE.value, # TBD! 2 No cartridge self-tests state 0, # 3 Consumable and cartridge installation state 0, # 4 Self-tests when the cartridge is dry state 0, # 5 Prime blood and dialysate circuits and run wet self-tests state 0, # 6 Re-circulate blood and dialysate circuits state 0, # 7 Patient connection state 0, 0 # TBD! late add ) self.demoCountdownId = MsgIds.MSG_ID_HD_NO_CART_SELF_TEST_PROGRESS self.demoSelection = 8 # Setup event to change to HD_PRE_TREATMENT_CART_INSTALL_STATE self.demoCount = 30 elif self.demoSelection == 8: # Cartridge Installation rsp_id = MsgIds.MSG_ID_PRE_TREATMENT_STATE_DATA rsp_payload = struct.pack( "<" + str(PreTreatmentSubModes.NUM_OF_HD_PRE_TREATMENT_STATES.value + 1) + "i", PreTreatmentSubModes.HD_PRE_TREATMENT_CART_INSTALL_STATE.value, 0, # 0 Water sample state 0, # 1 Consumable self-tests state 0, # 2 No cartridge self-tests state 0, # TBD! 3 Consumable and cartridge installation state 0, # 4 Self-tests when the cartridge is dry state 0, # 5 Prime blood and dialysate circuits and run wet self-tests state 0, # 6 Re-circulate blood and dialysate circuits state 0, # 7 Patient connection state 0, 0 # TBD! late add ) self.demoSelection = 0 # TBD! setup event to change to // Wait for user elif self.demoSelection == 9: # Dry Self Tests; init by MSG_ID_UI_INSTALLATION_CONFIRM rsp_id = MsgIds.MSG_ID_PRE_TREATMENT_STATE_DATA rsp_payload = struct.pack( "<" + str(PreTreatmentSubModes.NUM_OF_HD_PRE_TREATMENT_STATES.value + 1) + "i", PreTreatmentSubModes.HD_PRE_TREATMENT_SELF_TEST_DRY_STATE.value, 0, # 0 Water sample state 0, # 1 Consumable self-tests state 0, # 2 No cartridge self-tests state 0, # 3 Consumable and cartridge installation state self.demoTimedIncValue, # 4 Self-tests when the cartridge is dry state 0, # 5 Prime blood and dialysate circuits and run wet self-tests state 0, # 6 Re-circulate blood and dialysate circuits state 0, # 7 Patient connection state 0, 0 # TBD! late add ) if self.demoTimedIncValue >= PreTreatmentDrySelfTestsStates.NUM_OF_DRY_SELF_TESTS_STATES.value - 1: self.demoTimedIncValue = PreTreatmentPrimeStates.HD_PRIME_WAIT_FOR_USER_START_STATE.value - 1 # 1st pass is v+1 self.demoSelection = 10 # TBD! setup event to change to ... Wait for User self.demoCount = 0 # TBD! don't advance until user 'Start Prime' else: self.demoCountdownId = MsgIds.MSG_ID_HD_DRY_SELF_TEST_PROGRESS # TBD setup above: self.demoGroupCount = 14 * 4 # aggregate counter # DIALYSATE PRIME CIRCUITS ---------------------------------------- elif self.demoSelection == 10: # Prime circuits with dialysate rsp_id = MsgIds.MSG_ID_PRE_TREATMENT_STATE_DATA rsp_payload = struct.pack( "<" + str(PreTreatmentSubModes.NUM_OF_HD_PRE_TREATMENT_STATES.value + 1) + "i", PreTreatmentSubModes.HD_PRE_TREATMENT_PRIME_STATE.value, 0, # 2 No cartridge self-tests state 0, # 3 Consumable and cartridge installation state 0, # 0 Water sample state 0, # 1 Consumable self-tests state 0, # 4 Self-tests when the cartridge is dry state self.demoTimedIncValue, # 5 Prime blood and dialysate circuits and run wet self-tests state 0, # 6 Re-circulate blood and dialysate circuits state 0, # 7 Patient connection state 0, 0 # TBD! late add ) self.demoCountdownId = MsgIds.MSG_ID_HD_PRIMING_STATUS_DATA self.demoGroupCount = PreTreatmentPrimeStates.NUM_OF_HD_PRIME_STATES.value * 5 # aggregate count up to TBD! cleanup numbers if self.demoTimedIncValue == PreTreatmentPrimeStates.HD_PRIME_WAIT_FOR_USER_START_STATE.value: self.demoSelection = 0 # Wait for User # self.demoGroupCounter = 0 # aggregate counter # TBD! could skip HD_PRIME_PAUSE? elif self.demoTimedIncValue >= PreTreatmentPrimeStates.NUM_OF_HD_PRIME_STATES.value - 1: self.demoTimedIncValue = None ## TBD!! self.demoSelection = 11 # on next countdown transition self.demoCount = 0 # immediate timeout # TBD!! Hack to make counter neat self.demoGroupCounter = self.demoGroupCount # aggregate counter # TBD else: # TBD self.demoCountdownId = MsgIds.MSG_ID_HD_PRIMING_STATUS_DATA # TBD! need an aggregate time/timer elif self.demoSelection == 11: # Recirculate primed circuits rsp_id = MsgIds.MSG_ID_PRE_TREATMENT_STATE_DATA rsp_payload = struct.pack( "<" + str(PreTreatmentSubModes.NUM_OF_HD_PRE_TREATMENT_STATES.value + 1) + "i", PreTreatmentSubModes.HD_PRE_TREATMENT_RECIRCULATE_STATE.value, 0, # 0 Water sample state 0, # 1 Consumable self-tests state 0, # 2 No cartridge self-tests state 0, # 3 Consumable and cartridge installation state 0, # 4 Self-tests when the cartridge is dry state 0, # 5 Prime blood and dialysate circuits and run wet self-tests state PreTreatmentRecircStates.PRE_TREATMENT_RECIRC_STATE.value, # 6 Re-circulate blood and dialysate circuits state 0, # 7 Patient connection state 0, 0 # TBD! late add ) self.demoSelection = 0 # Then wait for User self.demoGroupCount = None # clear aggregate count # ## START TREATMENT GROUP ----------------------------------------- elif self.demoSelection == 20: # Start Treatment rsp_id = MsgIds.MSG_ID_HD_OP_MODE_DATA rsp_payload = integer_to_bytearray(HDOpModes.MODE_TREA.value) rsp_payload += integer_to_bytearray(0) self.demoCountdownId = MsgIds.MSG_ID_HD_BLOOD_PRIME_PROGRESS_DATA self.demoCount = 0 self.demoCounter = 1 # self.demoGroupCount = int(120 / 8) # TBD! fix const numbers # self.demoGroupCount = -1 self.demoSelection = 21 # Initiate Blood Prime elif self.demoSelection == 21: # Blood Prime rsp_id = MsgIds.MSG_ID_TREATMENT_STATE_DATA rsp_payload = struct.pack("<10i", TreatmentStates.TREATMENT_BLOOD_PRIME_STATE.value, # 0 Treatment Submode 0, # 1 UF state 0, # 2 Saline bolus state 0, # 3 Hep state 0, # 4 Rinseback state 0, # 5 Recirc state 0, # 6 Blood Prime state 0, # 7 End state 0, # 8 Stop state 0, # 9 Dialysis state ) self.demoSelection = 22 # Complete Blood Prime -> Dialysis self.demoCountdownId = MsgIds.MSG_ID_HD_BLOOD_PRIME_PROGRESS_DATA self.demoCount = int(120 / 8) # TBD! fix const numbers self.demoCounter = -1 # give slight delay # MSG_ID_PRESSURE_OCCLUSION_DATA ## TBD! combine with Treatment update data self.arterial_pressure_mmHg += (random() - 0.5) * 10 # TBD! replace magic wander numbers delta = self.arterial_pressure_mmHg - self.arterial_pressure_mid_mmHg if abs(delta) > 30: self.arterial_pressure_mmHg -= copysign(5, delta) delta = self.venous_pressure_mmHg - self.venous_pressure_mid_mmHg if abs(delta) > 30: self.venous_pressure_mmHg -= copysign(5, delta) self.cmd_set_pressure_occlusion_data(arterial_prs=self.arterial_pressure_mmHg, venous_prs=self.venous_pressure_mmHg, blood_pump_occlusion=0, dialysate_inlet_pump_occlusion=0, dialysate_outlet_pump_occlusion=0) ## DIALYSIS GROUP ------------------------------------------------- elif self.demoSelection == 22: # Dialysis # preset timing info self.treatment_start_time_s = time() self.cmd_set_treatment_time(sec_total=self.treatment_time_sec, sec_elapsed=0, sec_remain=self.treatment_time_sec) self.heparin_out_ml = self.heparin_bolus_ml if self.heparin_bolus_ml > 0: self.heparin_state = HeparinStates.HEPARIN_STATE_INITIAL_BOLUS # Initial heparin bolus delivery in progress else: self.heparin_state = HeparinStates.HEPARIN_STATE_STOPPED # Heparin delivery stopped by alarm or not yet started self.uf_state = TreatmentDialysisStates.DIALYSIS_START_STATE # 0 Treatment Submode self.demoCount = self.demoSpeed - 1 self.demoGroupCount = self.treatment_time_sec #TBD!! replace with user setting self.demoGroupCounter = 0 self.demoSelection = 29 # start dialysis elif self.demoSelection == 29: # Enter/Return to normal UF Dialysis rsp_id = MsgIds.MSG_ID_TREATMENT_STATE_DATA self.uf_state = TreatmentDialysisStates.DIALYSIS_UF_STATE # 1 UF state self.saline_state = SalineBolusStates.SALINE_BOLUS_STATE_IDLE # 2 Saline bolus state rsp_payload = struct.pack("<10i", TreatmentStates.TREATMENT_DIALYSIS_STATE.value, # 0 Treatment Submode self.uf_state.value, # 1 UF state self.saline_state.value, # 2 Saline bolus state self.heparin_state.value, # 3 Hep state 0, # 4 Rinseback state 0, # 5 Recirc state 0, # 6 Blood Prime state 0, # 7 End state 0, # 8 Stop state 0, # 9 Dialysis state ) self.demoSelection = 30 # ongoing data update elif self.demoSelection >= 30 and self.demoSelection < 40 : # Dialysis Data Update ---------------- self.blood_stop_time_s = time() # reset timer self.treatment_stop_time_s = self.blood_stop_time_s prev_heparin_state = self.heparin_state prev_saline_state = self.saline_state # MSG_ID_BLOOD_FLOW_DATA self.blood_flow_measured_ml_per_min += (random() - 0.5) * 10 # TBD! replace magic numbers if self.blood_flow_measured_ml_per_min > (self.blood_flow_set_point_ml_per_min + 30): self.blood_flow_measured_ml_per_min -= 5 self.cmd_set_treatment_blood_flow_rate(flow_set_pt=self.blood_flow_set_point_ml_per_min, measured_flow=self.blood_flow_measured_ml_per_min, rot_speed=0, mot_speed=0, mc_speed=0, mc_current=0, pwm=0, rotor_count=0) # MSG_ID_DIALYSATE_FLOW_DATA self.dialysate_flow_measured_ml_per_min += (random() - 0.5) * 5 # TBD! replace magic numbers if self.dialysate_flow_measured_ml_per_min > (self.dialysate_flow_set_point_ml_per_min + 30): self.dialysate_flow_measured_ml_per_min -= 5 self.cmd_set_treatment_dialysate_flow_rate(flow_set_pt=self.dialysate_flow_set_point_ml_per_min, measured_flow=self.dialysate_flow_measured_ml_per_min, rot_speed=0.0, mot_speed=0.0, mc_speed=0.0, mc_current=0.0, pwm=0.0) # MSG_ID_PRESSURE_OCCLUSION_DATA self.arterial_pressure_mmHg += (random() - 0.5) * 10 # TBD! replace magic wander numbers if self.arterial_pressure_mmHg > (self.arterial_pressure_mid_mmHg + 30): self.arterial_pressure_mmHg -= 5 self.venous_pressure_mmHg += (random() - 0.5) * 10 if self.venous_pressure_mmHg > (self.venous_pressure_mid_mmHg + 30): self.venous_pressure_mmHg -= 5 self.cmd_set_pressure_occlusion_data(arterial_prs=self.arterial_pressure_mmHg, venous_prs=self.venous_pressure_mmHg, blood_pump_occlusion=0, dialysate_inlet_pump_occlusion=0, dialysate_outlet_pump_occlusion=0) # MSG_ID_RTC_EPOCH # tbd! # MSG_ID_DIALYSATE_OUT_FLOW_DATA # TBD!! parameters seem swapped and off by 1000x ?? # self.cmd_set_treatment_ultrafiltration_outlet_flow_data(ref_uf_vol=self.uf_volume_set_l, # measured_uf_vol=self.uf_volume_out_l, # rot_speed=0.0, mot_speed=0.0, mc_speed=0.0, # mc_current=0.0, pwm=0.0) self.uf_volume_out_l = min(self.uf_volume_out_l + self.uf_rate_l_per_sec, self.uf_volume_set_l) self.cmd_set_treatment_ultrafiltration_outlet_flow_data(ref_uf_vol=self.uf_volume_out_l * 1000.0, measured_uf_vol=self.uf_volume_set_l * 1000.0, rot_speed=0.0, mot_speed=0.0, mc_speed=0.0, mc_current=0.0, pwm=0.0) # MSG_ID_LOAD_CELL_READINGS # MSG_ID_TREATMENT_TIME if self.treatment_time_sec != self.demoGroupCount: # handle user change in treatment time self.demoGroupCount = max(self.treatment_time_sec, self.demoGroupCounter) ctr_elapsed_sec = min(self.demoGroupCounter, self.demoGroupCount) ctr_remain_sec = self.demoGroupCount - self.demoGroupCounter self.cmd_set_treatment_time(sec_total=self.demoGroupCount, sec_elapsed=ctr_elapsed_sec, sec_remain=ctr_remain_sec) self.demoGroupCounter -= 1 # TBD! effectively freeze countdown MAYBE? # MSG_ID_HD_HEPARIN_DATA_BROADCAST if ctr_remain_sec < (self.heparin_stop_min * 60.0): self.heparin_state = HeparinStates.HEPARIN_STATE_COMPLETED else: if self.heparin_pause == False: self.heparin_out_ml += self.heparin_rate_ml_per_s * self.demoSpeed self.heparin_state = HeparinStates.HEPARIN_STATE_DISPENSING else: self.heparin_state = HeparinStates.HEPARIN_STATE_PAUSED self.heparin_expected_ml = (ctr_remain_sec - (self.heparin_stop_min * 60.0)) * self.heparin_rate_ml_per_s + self.heparin_out_ml self.cmd_set_treatment_heparin_data(cumulative=self.heparin_out_ml, required=self.heparin_expected_ml) # sub selections within Dialysis Data Update if self.demoSelection == 30: # Standard UF Dialysis . . . . . . . . . . self.demoGroupCounter += 1 # TBD! effectively unfreeze countdown MAYBE? if self.demoGroupCounter >= self.demoGroupCount: # end of Treatment self.demoGroupCount = None self.demoGroupCounter = 0 self.demoSelection = 40 elif self.demoSelection == 31: # Saline Bolus . . . . . . . . . . . . . self.uf_state = TreatmentDialysisStates.DIALYSIS_SALINE_BOLUS_STATE self.saline_state = SalineBolusStates.SALINE_BOLUS_STATE_IN_PROGRESS # 2 Saline bolus state self.demoSelection = 32 self.salineVolume_ml = 0.0 # reset elif self.demoSelection == 32: # Saline Bolus delivery rsp_id = MsgIds.MSG_ID_SALINE_BOLUS_DATA bolus_target_ml = self.demo_treatment_params.data_int[TreatmentParameters.TREATMENT_PARAM_SALINE_BOLUS_VOLUME_ML.value] new_volume_ml = bolus_target_ml - self.salineVolume_ml if self.salineBolusRate_mlpersec <= new_volume_ml: new_volume_ml = self.salineBolusRate_mlpersec # incremental else: self.demoSelection = 33 # done after this, so move on self.salineVolume_ml += new_volume_ml self.salineVolCum_ml += new_volume_ml rsp_payload = struct.pack("= 40) and (self.demoSelection < 50): set_rinseback_state = None new_volume_ml = self.rinsebackVelocity_ml_per_sec # incremental self.demoCount = 0 self.demoGroupCount = None # int(self.rinseback_volume_set_ml / self.rinsebackVelocity_ml_per_sec + 0.99) self.demoCountdownId = None if self.demoSelection == 40: # Rinseback stop before set_rinseback_state = TreatmentRinsebackStates.RINSEBACK_STOP_INIT_STATE self.rinseback_volume_out_ml = 0 self.demoSelection = 49 # then wait for user elif self.demoSelection == 41: # Rinseback running set_rinseback_state = TreatmentRinsebackStates.RINSEBACK_RUN_STATE self.demoSelection = 42 # next state elif self.demoSelection == 42: # Rinseback in process remain_volume_ml = self.rinseback_volume_set_ml - self.rinseback_volume_out_ml if remain_volume_ml <= new_volume_ml: new_volume_ml = remain_volume_ml self.demoSelection = 43 # done after this, so move on self.rinseback_volume_out_ml += new_volume_ml self.rinseback_volume_all_ml += new_volume_ml elif self.demoSelection == 43: # Rinseback stop after set_rinseback_state = TreatmentRinsebackStates.RINSEBACK_STOP_STATE self.demoSelection = 49 elif self.demoSelection == 44: # Rinseback pause set_rinseback_state = TreatmentRinsebackStates.RINSEBACK_PAUSED_STATE self.demoSelection = 49 elif self.demoSelection == 45: # Additional Rinseback set_rinseback_state = TreatmentRinsebackStates.RINSEBACK_RUN_ADDITIONAL_STATE self.demoSelection = 42 elif self.demoSelection == 49: # Rinseback wait for user pass # for all Rinseback states: if self.demoSelection <= 42: # actively/recently moving blood self.blood_stop_time_s = time() # reset timer rsp_id = MsgIds.MSG_ID_HD_RINSEBACK_PROGRESS used_time = max( 0, 120 - (int(time() - self.blood_stop_time_s))) rsp_payload = struct.pack("<2f 4i", self.rinseback_volume_set_ml, # 0 (float) the target volume in mL float(self.rinseback_volume_all_ml), # 1 (float) the current volume in mL int(new_volume_ml * 60), # 2 (uint ) the current flow rate in mL/min 120, # 3 (uint ) Total Timeout used_time, # 4 (uint ) Current Timeout count down (self.demoSelection == 43) # 5 (int/bool) complete ) tmr_message = DenaliMessage.build_message(channel_id=rsp_channel_id, # TBD!! dup code with below message_id=rsp_id.value, payload=rsp_payload) print("\nT send message:", MsgIds(rsp_id).name, tmr_message) self.can_interface.send(tmr_message, 0) # on Rinsback state changes: if set_rinseback_state is not None: rsp_id = MsgIds.MSG_ID_TREATMENT_STATE_DATA rsp_payload = struct.pack("<10i", TreatmentStates.TREATMENT_RINSEBACK_STATE.value, # 0 Treatment Submode TreatmentDialysisStates.DIALYSIS_UF_STATE.value, # 1 UF state 0, # 2 Saline bolus state 0, # 3 Hep state set_rinseback_state.value, # 4 Rinseback state 0, # 5 Recirc state 0, # 6 Blood Prime state 0, # 7 End state 0, # 8 Stop state 0, # 9 Dialysis state ) elif self.demoSelection == 50: rsp_id = MsgIds.MSG_ID_HD_OP_MODE_DATA rsp_payload = integer_to_bytearray(HDOpModes.MODE_STAN.value) rsp_payload += integer_to_bytearray(HDStandbyStates.STANDBY_WAIT_FOR_DISINFECT_STATE.value) self.demoSelection = 0 # wait for user # x94 MSG_ID_HD_TREATMENT_LOG_PERIODIC_DATA, # 1, 2, # 20.891, 29.456, 30.567 # x95 MSG_ID_HD_TREATMENT_LOG_ALARM_EVENT, # 0, 41.100, 42.200 # x96 MSG_ID_HD_TREATMENT_LOG_EVENT, # 0,51.100,51.200 # x74 MSG_ID_HD_DISPOSABLE_REMOVAL_CONFIRM_RESPONSE,1,0 ## ## ## Output counter information, if active if self.demoCountdownId is not None: # TBD Countdown ctr_id = self.demoCountdownId if self.demoGroupCount is None: ctr_count = self.demoCount ctr_counter = self.demoCounter else: ctr_count = self.demoGroupCount ctr_counter = self.demoGroupCounter if ctr_counter >= ctr_count: # TBD!! automatically disable group count after it reaches goal self.demoCountdownId = None self.demoGroupCount = None self.demoGroupCounter = 0 if ctr_count < 0: # TBD! shouldn't happen ctr_count = 0 ctr_counter = max(0, min(ctr_count, ctr_counter)) # this allows for negative start values to Prime display and avoids overrun if ctr_id != MsgIds.MSG_ID_HD_BLOOD_PRIME_PROGRESS_DATA and\ ctr_id != MsgIds.MSG_ID_HD_RINSEBACK_PROGRESS: ctr_payload = integer_to_bytearray(ctr_count) ctr_payload += integer_to_bytearray(ctr_count - ctr_counter) else: # MSG_ID_HD_BLOOD_PRIME_PROGRESS has special scaling using floats@ 8mL/sec TBD set as const ctr_payload = float_to_bytearray(ctr_count * 8.0) ctr_payload += float_to_bytearray(ctr_counter * 8.0) ctr_payload += float_to_bytearray(0.0) # where to send message from if self.demoCountdownChannel is None: ctr_channel_id = DenaliChannels.hd_to_ui_ch_id # default else: ctr_channel_id = self.demoCountdownChannel ctr_message = DenaliMessage.build_message(channel_id=ctr_channel_id, message_id=ctr_id.value, payload=ctr_payload) print("\nC[", self.demoSelection, "] send message:", MsgIds(ctr_id).name, ctr_message) self.can_interface.send(ctr_message, 0) # # Send Response if needed if rsp_id is not None: tmr_message = DenaliMessage.build_message(channel_id=rsp_channel_id, message_id=rsp_id.value, payload=rsp_payload) print("\nT send message:", MsgIds(rsp_id).name, tmr_message) self.can_interface.send(tmr_message, 0) self.demoSemaphore.release() def _send_treatment_log(self) -> None: """ Handles a UI request to set the HD RTC @param message: (dict) the message containing the request @return: None """ tx_time_s = self.treatment_stop_time_s - self.treatment_start_time_s self.cmd_send_post_treatment_log_response( accepted=EResponse.Accepted, # bool reason=RequestRejectReasons.REQUEST_REJECT_REASON_NONE.value, # int, blood_flow_rate=self.demo_treatment_params.data_int[ TreatmentParameters.TREATMENT_PARAM_BLOOD_FLOW_RATE_ML_MIN.value], # int dialysate_flow_rate=self.demo_treatment_params.data_int[ TreatmentParameters.TREATMENT_PARAM_DIALYSATE_FLOW_RATE_ML_MIN.value], # int treatment_duration=self.demo_treatment_params.data_int[ TreatmentParameters.TREATMENT_PARAM_TREATMENT_DURATION_MIN.value] * 60, # int actual_treatment_duration=int(tx_time_s), # int acid_concentrate_type=self.demo_treatment_params.data_int[ TreatmentParameters.TREATMENT_PARAM_ACID_CONCENTRATE.value], # int bicarbonate_concentrate_type=self.demo_treatment_params.data_int[ TreatmentParameters.TREATMENT_PARAM_BICARB_CONCENTRATE.value], # int potassium_concentration=1000, # int calcium_concentration=2500, # int bicarbonate_concentration=33000, # int sodium_concentration=137000, # int dialysate_temperature=self.demo_treatment_params.data_float[ TreatmentParameters.TREATMENT_PARAM_DIALYSATE_TEMPERATURE_C.value], # float dialyzer_type=self.demo_treatment_params.data_int[TreatmentParameters.TREATMENT_PARAM_DIALYZER_TYPE.value], # int treatment_start_date_time=int(self.treatment_start_time_s), # int treatment_end_date_time=int(self.treatment_stop_time_s), # int average_blood_flow=self.demo_treatment_params.data_int[ TreatmentParameters.TREATMENT_PARAM_BLOOD_FLOW_RATE_ML_MIN.value], # float average_dialysate_flow=self.demo_treatment_params.data_int[ TreatmentParameters.TREATMENT_PARAM_DIALYSATE_FLOW_RATE_ML_MIN.value], # float dialysate_volume_used=(self.demo_treatment_params.data_int[ TreatmentParameters.TREATMENT_PARAM_DIALYSATE_FLOW_RATE_ML_MIN.value] * tx_time_s / 60.0 / 1000.0), # float average_dialysate_temp=self.demo_treatment_params.data_float[ TreatmentParameters.TREATMENT_PARAM_DIALYSATE_TEMPERATURE_C.value], # float origin_uf_volume=self.uf_volume_set_l, # float target_uf_volume=self.uf_volume_set_l, # float actual_uf_volume=self.uf_volume_out_l, # float origin_uf_rate=self.uf_volume_set_l / self.demo_treatment_params.data_int[ TreatmentParameters.TREATMENT_PARAM_TREATMENT_DURATION_MIN.value], # float target_uf_rate=self.uf_volume_set_l / self.demo_treatment_params.data_int[ TreatmentParameters.TREATMENT_PARAM_TREATMENT_DURATION_MIN.value], # float actual_uf_rate=self.uf_volume_out_l / tx_time_s * 60.0, # float saline_bolus_volume=int(self.salineVolCum_ml), # int heparin_bolus_volume=self.heparin_bolus_ml, # float heparin_dispense_rate=self.heparin_rate_ml_per_hr, # float heparin_pre_stop=self.demo_treatment_params.data_int[ TreatmentParameters.TREATMENT_PARAM_HEPARIN_PRESTOP_MIN.value], # int heparin_delivered_volume=self.heparin_out_ml, # float heparin_type=self.demo_treatment_params.data_int[TreatmentParameters.TREATMENT_PARAM_HEPARIN_TYPE.value], # int average_arterial_pressure=self.arterial_pressure_mid_mmHg, # float average_venous_pressure=self.venous_pressure_mid_mmHg, # float device_id=101, # int water_sample_test_result=EResponse.Accepted # int ) def _handler_set_rtc_request(self, message: dict) -> None: """ Handles a UI request to set the HD RTC @param message: (dict) the message containing the request @return: None """ epoch = struct.unpack('i', bytearray( message['message'][MsgFieldPositions.START_POS_FIELD_1:MsgFieldPositions.END_POS_FIELD_1]))[0] self.logger.debug("Request to set the HD epoch to {0}".format(epoch)) self.cmd_send_set_rtc_response(YES, 0) def cmd_send_set_rtc_response(self, response, reason): """ Sends a set RTC response message @param response: (int) 0=NO, 1=YES @param reason: the rejection reason @return: None """ self.logger.debug("HD: Sending response {0} reason {1}".format(response, reason)) payload = integer_to_bytearray(response) payload += integer_to_bytearray(reason) message = DenaliMessage.build_message(channel_id=DenaliChannels.hd_to_ui_ch_id, message_id=MsgIds.MSG_ID_HD_UI_SET_RTC_RESPONSE.value, payload=payload) self.can_interface.send(message, 0) def cmd_send_treatment_parameter_validation_response(self, rejections: List[RequestRejectReasons]): """ Sends a treatment parameter validation response @param rejections: A list of rejection code enums @return: True if successful, False otherwise """ if len(rejections) != self.NUM_TREATMENT_PARAMETERS: self.logger.error("Invalid number of treatment parameter enums provided.") return False if not all([isinstance(each, enum.Enum) for each in rejections]): self.logger.error("Not all rejections are enums.") return False payload = bytearray() for rejection in rejections: payload += integer_to_bytearray(rejection.value) message = DenaliMessage.build_message(channel_id=DenaliChannels.hd_to_ui_ch_id, message_id=MsgIds.MSG_ID_HD_NEW_TREATMENT_PARAMS_RESPONSE.value, payload=payload) # Send message self.can_interface.send(message, 0) return True def cmd_send_treatment_parameter_manual_validation_response(self, rejections: List[int]): """ Sends a manually built treatment parameter validation response @param rejections: A list of rejection code enums @return: True if successful, False otherwise """ if len(rejections) != self.NUM_TREATMENT_PARAMETERS: self.logger.error("Invalid number of treatment parameter enums provided.") return False if not all([isinstance(each, int) for each in rejections]): self.logger.error("Not all rejections are the correct type.") return False payload = bytearray() for rejection in rejections: payload += integer_to_bytearray(rejection) message = DenaliMessage.build_message(channel_id=DenaliChannels.hd_to_ui_ch_id, message_id=MsgIds.MSG_ID_HD_NEW_TREATMENT_PARAMS_RESPONSE.value, payload=payload) # Send message self.can_interface.send(message, 0) return True def cmd_send_poweroff_command(self): """ Broadcast that the poweroff command @return: None """ payload = byte_to_bytearray(PowerOffCommands.PW_COMMAND_OPEN.value) message = DenaliMessage.build_message(channel_id=DenaliChannels.hd_to_ui_ch_id, message_id=MsgIds.MSG_ID_POWER_OFF.value, payload=payload) self.can_interface.send(message, 0) def cmd_send_poweroff_timeout(self): """ Sends a poweroff timeout @return: None """ payload = byte_to_bytearray(PowerOffCommands.PW_TIMEOUT_CLOSE.value) message = DenaliMessage.build_message(channel_id=DenaliChannels.hd_to_ui_ch_id, message_id=MsgIds.MSG_ID_POWER_OFF.value, payload=payload) self.can_interface.send(message, 0) def cmd_send_poweroff_reject(self): """ Sends a poweroff reject @return: None """ payload = byte_to_bytearray(PowerOffCommands.PW_REJECT_SHOW.value) message = DenaliMessage.build_message(channel_id=DenaliChannels.hd_to_ui_ch_id, message_id=MsgIds.MSG_ID_POWER_OFF.value, payload=payload) self.can_interface.send(message, 0) def cmd_send_poweroff_imminent(self): """ Broadcast that the system will shut down @return: None """ message = DenaliMessage.build_message(channel_id=DenaliChannels.hd_sync_broadcast_ch_id, message_id=MsgIds.MSG_ID_POWER_OFF_IMMINENT.value, payload=None) self.can_interface.send(message, 0) def _handler_ui_confirm_treatment(self, message): """ Handler function to detect when a treatment is confirmed @param message: the confirm treatment message @return: None """ request = struct.unpack('i', bytearray( message['message'][MsgFieldPositions.START_POS_FIELD_1:MsgFieldPositions.END_POS_FIELD_1]))[0] if request == 0: self.logger.debug("Received UI cancel confirmation of Treatment Parameters. ") return self.logger.debug("Received UI confirmation of Treatment Parameters. ") self.logger.debug("Priming ...") state = 0 total_seconds = 100 for seconds_remaining in range(total_seconds, -1, -1): if seconds_remaining % (total_seconds // 3) == 0: state += 1 # TBD!!! NOT DEFINED: self.cmd_send_priming_time_remaining(state, seconds_remaining, total_seconds) sleep(0.05) self.logger.debug("Finished priming.") def _handler_ui_pre_treatment_uf_request(self, message): """ Handles the ui pre treatment uf request and sends a response @param message: The ui pretreatment uf request message @return: None """ uf_volume = struct.unpack('f', bytearray( message['message'][MsgFieldPositions.START_POS_FIELD_1:MsgFieldPositions.END_POS_FIELD_1]))[0] self.logger.debug("Received UF Volume: {0} mL".format(uf_volume)) self.cmd_send_uf_treatment_response(1, 0, uf_volume) def _handler_ui_initiate_treatment(self, message): """ Handler function to start a treatment @param message: the start treatment message @return: None """ request = struct.unpack('i', bytearray( message['message'][MsgFieldPositions.START_POS_FIELD_1:MsgFieldPositions.END_POS_FIELD_1]))[0] if request == 0: self.logger.debug("Selecting treatment parameters") self.cmd_send_hd_operation_mode(HDOpModes.MODE_PRET.value) elif request == 1: self.logger.debug("Canceling treatment") self.cmd_send_hd_operation_mode(HDOpModes.MODE_STAN.value) elif request == 2: self.logger.debug("Starting treatment") self.cmd_send_hd_operation_mode(HDOpModes.MODE_TREA.value) self.cmd_initiate_treatment_response(YES, 0) def cmd_initiate_treatment_response(self, response: int, reason: int): """ Sends a start treatment response message @param response: 0=NO, 1=YES @param reason: the rejection reason @return: None """ self.logger.debug("Sending: {0}".format(response)) payload = integer_to_bytearray(response) payload += integer_to_bytearray(reason) message = DenaliMessage.build_message(channel_id=DenaliChannels.hd_to_ui_ch_id, message_id=MsgIds.MSG_ID_HD_INITIATE_TREATMENT_RESPONSE.value, payload=payload) self.can_interface.send(message, 0) def cmd_send_hd_operation_mode(self, op_mode: int, sub_mode: int = 0): """ Broadcasts the current HD operation mode @param op_mode: hd operation mode @param sub_mode: hd operation sub-mode @return: None """ if not isinstance(op_mode, int): raise ValueError("Provided mode is not of type 'int'") if not isinstance(sub_mode, int): raise ValueError("Provided mode is not of type 'int'") payload = integer_to_bytearray(op_mode) payload += integer_to_bytearray(sub_mode) message = DenaliMessage.build_message(channel_id=DenaliChannels.hd_sync_broadcast_ch_id, message_id=MsgIds.MSG_ID_HD_OP_MODE.value, payload=payload) self.can_interface.send(message, 0) def cmd_send_uf_treatment_response(self, accepted, reason, volume): """ Sends the uf volume adjustment response message in pre-treatment @param accepted: (uint) the acceptance, 1 = accepted, 0 = rejected @param reason: (uint) the reason for rejection @param volume: (float) the acceptable/accepted ultrafiltration volume @return: none """ payload = integer_to_bytearray(accepted) payload += integer_to_bytearray(reason) payload += float_to_bytearray(volume) message = DenaliMessage.build_message(channel_id=DenaliChannels.hd_to_ui_ch_id, message_id=MsgIds.MSG_ID_HD_SET_UF_VOLUME_PARAMETER_RESPONSE.value, payload=payload) self.can_interface.send(message, 0) def cmd_send_end_treatment_response(self): """ Sends an end treatment response @return: None """ payload = integer_to_bytearray(YES) message = DenaliMessage.build_message(channel_id=DenaliChannels.hd_to_ui_ch_id, message_id=MsgIds.MSG_ID_HD_TX_END_CMD_RESPONSE.value, payload=payload) self.can_interface.send(message, 0) def _handler_ui_validate_parameters(self) -> None: """ handler for UI parameters validation @return: None """ rejections = [ self.treatment_parameter_rejections.param_request_valid, self.treatment_parameter_rejections.param_blood_flow_rate, self.treatment_parameter_rejections.param_dialysate_flow_rate, self.treatment_parameter_rejections.param_duration, self.treatment_parameter_rejections.param_heparin_stop_time, self.treatment_parameter_rejections.param_saline_bolus, self.treatment_parameter_rejections.param_acid_concentrate, self.treatment_parameter_rejections.param_bicarbonate_concentrate, self.treatment_parameter_rejections.param_dialyzer_type, self.treatment_parameter_rejections.param_blood_pressure_measure_interval, self.treatment_parameter_rejections.param_rinseback_flow_rate, self.treatment_parameter_rejections.param_arterial_pressure_limit_low, self.treatment_parameter_rejections.param_arterial_pressure_limit_high, self.treatment_parameter_rejections.param_venous_pressure_limit_low, self.treatment_parameter_rejections.param_venous_pressure_limit_high, self.treatment_parameter_rejections.param_heparin_dispensing_rate, self.treatment_parameter_rejections.param_heparin_bolus_volume, self.treatment_parameter_rejections.param_dialysate_temp ] self.cmd_send_treatment_parameter_validation_response(rejections) def test_started(self, test_name: str): """ Logs that a test was started @param test_name: The name of the test @return: None """ self.logger.info("Test Started: {0}".format(test_name)) def test_completed(self): """ Logs that a test was completed @return: None """ self.logger.info("Test Completed") def cmd_send_acknowledge_hd(self): """ the acknowledge from HD @return: none """ payload = ["A5", "01", "00", "FF", "FF", "00", "19", "00"] payload = [int(each, 16) for each in payload] message = {"channel_id": DenaliChannels.hd_to_ui_ch_id, "message": payload} self.can_interface.send(message, 0) def cmd_send_acknowledge_ui(self): """ the acknowledge from UI @return: none """ payload = ["A5", "01", "00", "FF", "FF", "00", "19", "00"] payload = [int(each, 16) for each in payload] message = {"channel_id": DenaliChannels.ui_to_hd_ch_id, "message": payload} self.can_interface.send(message, 0) def cmd_show_poweroff_dialog(self): """ the message from HD to UI to show the power off dialog @return: none """ payload = ["A5", "01", "00", "01", "00", "01", "00", "38"] payload = [int(each, 16) for each in payload] message = {"channel_id": DenaliChannels.hd_to_ui_ch_id, "message": payload} self.can_interface.send(message, 0) def cmd_hide_poweroff_dialog(self): """ the message from HD to UI to hide the power off dialog @return: none """ payload = ["A5", "01", "00", "01", "00", "01", "01", "09"] payload = [int(each, 16) for each in payload] message = {"channel_id": DenaliChannels.hd_to_ui_ch_id, "message": payload} self.can_interface.send(message, 0) def cmd_show_poweroff_notification_dialog(self): """ the message from HD to UI to show the shutting down notification box @return: none """ payload = ["A5", "01", "00", "0E", "00", "00", "24", "00"] payload = [int(each, 16) for each in payload] message = {"channel_id": DenaliChannels.hd_sync_broadcast_ch_id, "message": payload} self.can_interface.send(message, 0) def cmd_show_poweroff_rejection_dialog(self): """ the message from HD to UI to show the power off dialog @return: none """ payload = ["A5", "01", "00", "01", "00", "01", "02", "5A"] payload = [int(each, 16) for each in payload] message = {"channel_id": DenaliChannels.hd_to_ui_ch_id, "message": payload} self.can_interface.send(message, 0) @staticmethod def wait_for_message_to_be_sent(delay=0.050): """ After each multi-frame message put a 50ms sleep, time.sleep(0.1) it seems it's needed otherwise the test will check a value which has not been received yet. :@param delay: the number of seconds to wait @return: none """ sleep(delay) @staticmethod def build_hd_debug_text(vtext): """ the debug text message from HD builder method @param vtext: (str) the debug text @return: none """ message_length = 40 txt = messageBuilder.textToByte(vtext, message_length) # + 1 null term msg = messageBuilder.buildMessage(GuiActionType.HDDebugText, 1 * (message_length + 1), False, txt) return messageBuilder.toFrames(msg) @staticmethod def cmd_set_hd_debug_text(debug_text): """ the debug text message from HD setter/sender method @param debug_text: (str) the debug text @return: none """ frames = HDSimulator.build_hd_debug_text(debug_text) frames = messageBuilder.toCandumpFormat(frames) for frame in frames: subprocess.call(['cansend', 'can0', '020#{}'.format(frame)]) HDSimulator.wait_for_message_to_be_sent() def cmd_set_treatment_parameter_ranges(self, min_treatment_duration: int, max_treatment_duration: int, min_uf_volume: float, max_uf_volume: float, min_dialysate_flow_rate: int, max_dialysate_flow_rate: int) -> None: """ The Treatment adjustment parameter ranges data message setter/sender method | MSG | CAN ID | Box | Type | Ack | Src | Dst | Description | #1:(U32) | #2:(U32) | #3:(F32) | #4:(F32) | #5:(U32) | #6:(U32) | |:----:|:------:|:---:|:------:|:---:|:---:|:---:|:-----------: |:--: |:--: |:--: |:--: |:--: |:--: | |0x1A00| 0x020 | 6 | 1/60 Hz| Y | HD | UI | Treatment adjustment param ranges Data | \ref Data::mDuration_Min | \ref Data::mDuration_Max | \ref Data::mUltrafiltration_Volume_Min | \ref Data::mUltrafiltration_Volume_Max | \ref Data::mDialysate_Flow_Min | \ref Data::mDialysate_Flow_Max | @param min_treatment_duration: (int) Min Treatment Duration @param max_treatment_duration: (int) Max Treatment Duration @param min_uf_volume: (float) Min UF Volume @param max_uf_volume: (float) Max UF Volume @param min_dialysate_flow_rate: (int) Min Dialysate Flow Rate @param max_dialysate_flow_rate: (int) Max Dialysate Flow Rate @return: None """ payload = integer_to_bytearray(min_treatment_duration) payload += integer_to_bytearray(max_treatment_duration) payload += float_to_bytearray(min_uf_volume) payload += float_to_bytearray(max_uf_volume) payload += integer_to_bytearray(min_dialysate_flow_rate) payload += integer_to_bytearray(max_dialysate_flow_rate) message = DenaliMessage.build_message(channel_id=DenaliChannels.hd_to_ui_ch_id, message_id=MsgIds.MSG_ID_TREATMENT_PARAM_CHANGE_RANGES_DATA.value, payload=payload) self.can_interface.send(message, 0) def cmd_set_treatment_blood_flow_rate(self, flow_set_pt: int, measured_flow: float, rot_speed: float, mot_speed: float, mc_speed: float, mc_current: float, pwm: float, rotor_count: int, presFlow: int, rotorHall: int) -> None: """ The Blood Flow Data message setter/sender method | MSG | CAN ID | Box | Type | Ack | Src | Dst | Description | #1:(S32) | #2:(F32) | #3:(F32) | #4:(F32) | #5:(F32) | #6:(F32) | #7:(F32) | #8:(U32) | |:----:|:------:|:---:|:------:|:---:|:---:|:---:|:-----------: |:--: |:--: |:--: |:--: |:--: |:--: |:--: |:--: | |0x0500| 0x040 | 7 | 1 Hz | N | HD | All | Blood Flow Data | \ref Data::mFlowSetPoint | \ref Data::mMeasuredFlow | \ref Data::mRotorSpeed | \ref Data::mMotorSpeed | \ref Data::mMotorCtlSpeed | \ref Data::mMotorCtlCurrent | \ref Data::mPWMDutyCycle | \ref Data::mRotorCount | @param flow_set_pt: (int) Flow Set Point @param measured_flow: (float) Measured Flow @param rot_speed: (float) Rot Speed @param mot_speed: (float) Motor Speed @param mc_speed: (float) MC Speed @param mc_current: (float) MC Current @param pwm: (float) PWM @param rotor_count: (int) Rotor Count @param presFlow: (int) Prescribed Blood Flow @param rotorHall: (int) RotorHall sensor @return: None """ payload = integer_to_bytearray(flow_set_pt) payload += float_to_bytearray(measured_flow) payload += float_to_bytearray(rot_speed) payload += float_to_bytearray(mot_speed) payload += float_to_bytearray(mc_speed) payload += float_to_bytearray(mc_current) payload += float_to_bytearray(pwm) payload += integer_to_bytearray(rotor_count) payload += integer_to_bytearray(presFlow) payload += integer_to_bytearray(rotorHall) message = DenaliMessage.build_message(channel_id=DenaliChannels.hd_sync_broadcast_ch_id, message_id=MsgIds.MSG_ID_BLOOD_FLOW_DATA.value, payload=payload) self.can_interface.send(message, 0) def cmd_set_treatment_dialysate_flow_rate(self, flow_set_pt: int, measured_flow: float, rot_speed: float, mot_speed: float, mc_speed: float, mc_current: float, pwm: float, rotor_count: int, presFlow: int, rotorHall: int) -> None: """ The Dialysate Flow Data message setter/sender method | MSG | CAN ID | Box | Type | Ack | Src | Dst | Description | #1:(S32) | #2:(F32) | #3:(F32) | #4:(F32) | #5:(F32) | #6:(F32) | #7:(F32) | |:----:|:------:|:---:|:------:|:---:|:---:|:---:|:-----------: |:--: |:--: |:--: |:--: |:--: |:--: |:--: | |0x0800| 0x040 | 7 | 1 Hz | N | HD | All | Dialysate Flow Data | mFlowSetPoint | mMeasuredFlow | mRotorSpeed | mMotorSpeed | mMotorCtlSpeed | mMotorCtlCurrent | mPWMDutyCycle | @param flow_set_pt: (signed int) Flow Set Point @param measured_flow: (float) Measured Flow @param rot_speed: (float) Rot Speed @param mot_speed: (float) Motor Speed @param mc_speed: (float) MC Speed @param mc_current: (float) MC Current @param pwm: (float) PWM @param rotor_count: (int) Rotor Count @param presFlow: (int) Prescribed Blood Flow @param rotorHall: (int) RotorHall sensor @return: None """ payload = integer_to_bytearray(flow_set_pt) payload += float_to_bytearray(measured_flow) payload += float_to_bytearray(rot_speed) payload += float_to_bytearray(mot_speed) payload += float_to_bytearray(mc_speed) payload += float_to_bytearray(mc_current) payload += float_to_bytearray(pwm) payload += integer_to_bytearray(rotor_count) payload += integer_to_bytearray(presFlow) payload += integer_to_bytearray(rotorHall) message = DenaliMessage.build_message(channel_id=DenaliChannels.hd_sync_broadcast_ch_id, message_id=MsgIds.MSG_ID_DIALYSATE_FLOW_DATA.value, payload=payload) self.can_interface.send(message, 0) def cmd_send_treatment_adjust_blood_dialysate_response(self, accepted: int, reason: int, blood_rate: int, dialysate_flow_rate: int) -> None: """ The Blood/dialysate rate change Response message setter/sender method | MSG | CAN ID | Box | Type | Ack | Src | Dst | Description | #1:(U32) | #2:(U32) | #3:(U32) | #4:(U32) | |:----:|:------:|:---:|:------:|:---:|:---:|:---:|:-----------: |:--: |:--: |:--: |:--: | |0x1800| 0x020 | 6 | Rsp | Y | HD | UI | Blood/dialysate rate change Response | \ref Data::mAccepted | \ref Data::mReason | \ref Data::mBloodRate | \ref Data::mDialysateRate | @param accepted: (int) boolean accept/reject response @param reason: (int) rejection reason @param blood_rate: (int) Blood Flow Rate @param dialysate_flow_rate: (int) Dialysate Flow Rate @return: None """ if not isinstance(accepted, int): accepted = int(accepted) if not isinstance(reason, int): reason = int(reason) if not isinstance(blood_rate, int): blood_rate = int(blood_rate) if not isinstance(dialysate_flow_rate, int): dialysate_flow_rate = int(dialysate_flow_rate) payload = integer_to_bytearray(accepted) payload += integer_to_bytearray(reason) payload += integer_to_bytearray(blood_rate) payload += integer_to_bytearray(dialysate_flow_rate) message = DenaliMessage.build_message(channel_id=DenaliChannels.hd_to_ui_ch_id, message_id=MsgIds.MSG_ID_USER_BLOOD_DIAL_RATE_CHANGE_RESPONSE.value, payload=payload) self.can_interface.send(message, 0) def cmd_send_treatment_adjust_duration_response(self, accepted: int, reason: int, duration: int, ultrafiltration: float) -> None: """ The Treatment Duration change Response message setter/sender method | MSG | CAN ID | Box | Type | Ack | Src | Dst | Description | #1:(U32) | #2:(U32) | #3:(U32) | #5:(F32) | |:----:|:------:|:---:|:------:|:---:|:---:|:---:|:-----------: |:--: |:--: |:--: |:--: | |0x1B00| 0x020 | 6 | Rsp | Y | HD | UI | Treatment Duration change Response | \ref Data::mAccepted | \ref Data::mReason | \ref Data::mDuration | \ref Data::mUFVolume | @param accepted: (int) boolean accept/reject response @param reason: (int) rejection reason @param duration: (int) Treatment Duration @param ultrafiltration: (float) Ultrafiltration Volume @return: none """ payload = integer_to_bytearray(accepted) payload += integer_to_bytearray(reason) payload += integer_to_bytearray(duration) payload += float_to_bytearray(ultrafiltration) message = DenaliMessage.build_message(channel_id=DenaliChannels.hd_to_ui_ch_id, message_id=MsgIds.MSG_ID_USER_TREATMENT_TIME_CHANGE_RESPONSE.value, payload=payload) self.can_interface.send(message, 0) def cmd_set_treatment_adjust_ultrafiltration_state_response(self, accepted: int, reason: int, state: int) -> None: """ the Treatment ultrafiltration adjustment response message setter/sender method @param accepted: (int) boolean accept/reject response @param reason: (int) rejection reason @param state: (int) Ultrafiltration State @return: none """ payload = integer_to_bytearray(accepted) payload += integer_to_bytearray(reason) payload += integer_to_bytearray(state) message = DenaliMessage.build_message(channel_id=DenaliChannels.hd_to_ui_ch_id, message_id=MsgIds.MSG_ID_USER_UF_PAUSE_RESUME_RESPONSE.value, payload=payload) self.can_interface.send(message, 0) def cmd_set_treatment_adjust_ultrafiltration_accepted(self, state: int) -> None: """ a convenient method for cmd_set_treatment_adjust_ultrafiltration_state_response which sends accept true @param state: (int) Ultrafiltration State @return: none """ self.cmd_set_treatment_adjust_ultrafiltration_state_response(EResponse.Accepted, 0, state) def cmd_set_treatment_adjust_ultrafiltration_rejected(self, reason: int, state: int) -> None: """ a convenient method for cmd_set_treatment_adjust_ultrafiltration_state_response which sends accept false @param reason: (int) rejection reason @param state: (int) Ultrafiltration State @return: none """ self.cmd_set_treatment_adjust_ultrafiltration_state_response(EResponse.Rejected, reason, state) def cmd_set_treatment_adjust_ultrafiltration_init_response(self, accepted: int, reason: int, volume: float) -> None: """ the ultrafiltration volume change response message setter/sender method | MSG | CAN ID | Box | Type | Ack | Src | Dst | Description | #1:(U32) | #2:(U32) | #3:(F32) |:----:|:------:|:---:|:------:|:---:|:---:|:---:|:-----------: |:--: |:--: |:--: |0x5000| 0x020 | 6 | Rsp | Y | HD | UI | Pre UF Volume Adjustment Response | \ref Data::mAccepted | \ref Data::mReason | \ref Data::mVolume @param accepted: (int) boolean accept/reject response @param reason: (int) rejection reason @param volume: (float) Ultrafiltration Volume @return: none """ payload = integer_to_bytearray(accepted) payload += integer_to_bytearray(reason) payload += float_to_bytearray(volume) message = DenaliMessage.build_message(channel_id=DenaliChannels.hd_to_ui_ch_id, message_id=MsgIds.MSG_ID_HD_SET_UF_VOLUME_PARAMETER_RESPONSE.value, payload=payload) self.can_interface.send(message, 0) def cmd_set_treatment_adjust_ultrafiltration_edit_response(self, accepted: int, reason: int, volume: float, duration: int, duration_diff: int, rate: float, rate_diff: float, rate_old: float) -> None: """ the ultrafiltration volume change response message setter/sender method | MSG | CAN ID | Box | Type | Ack | Src | Dst | Description | #1:(U32) | #2:(U32) | #1:(U32) | #2:(U32) | #3:(F32) | #4:(U32) | #5:(F32) | #6:(U32) | #7:(U32) | #8:(F32) | |:----:|:------:|:---:|:------:|:---:|:---:|:---:|:-----------: |:--: |:--: |:--: |:--: |:--: |:--: |:--: |:--: |:--: |:--: | |0x1300| 0x020 | 6 | Rsp | Y | HD | UI | UF Vol. Change Response | \ref Data::mAccepted | \ref Data::mReason | \ref Data::mAccepted | \ref Data::mReason | \ref Data::mVolume | \ref Data::mDuration | \ref Data::mRate | \ref Data::mDurationDiff | \ref Data::mRateDiff | \ref Data::mRateOld | @param accepted: (int) boolean accept/reject response @param reason: (int) rejection reason @param volume: (float) Ultrafiltration Volume @param duration: (int) Treatment Duration @param duration_diff: (int) Duration Difference @param rate: (float) Ultrafiltration Rate @param rate_diff: (float) Ultrafiltration Rate Difference @param rate_old: (float) Ultrafiltration Rate Old @return: none """ payload = integer_to_bytearray(accepted) payload += integer_to_bytearray(reason) payload += float_to_bytearray(volume) payload += integer_to_bytearray(duration) payload += integer_to_bytearray(duration_diff) payload += float_to_bytearray(rate) payload += float_to_bytearray(rate_diff) payload += float_to_bytearray(rate_old) message = DenaliMessage.build_message(channel_id=DenaliChannels.hd_to_ui_ch_id, message_id=MsgIds.MSG_ID_USER_UF_SETTINGS_CHANGE_RESPONSE.value, payload=payload) self.can_interface.send(message, 0) def cmd_set_treatment_adjust_ultrafiltration_edit_rejected(self, reason: int) -> None: """ a convenient method for cmd_set_treatment_adjust_ultrafiltration_edit_response which only sends a rejection reason and sends other values all as zero @param reason: (int) rejection reason @return: none """ self.cmd_set_treatment_adjust_ultrafiltration_edit_response(0, reason, 0, 0, 0, 0, 0, 0) def cmd_set_treatment_adjust_ultrafiltration_confirm_response(self, accepted: int, reason: int, volume: float, duration: int, rate: float) -> None: """ the ultrafiltration volume Change Confirmation Response message setter/sender method | MSG | CAN ID | Box | Type | Ack | Src | Dst | Description | #1:(U32) | #2:(U32) | #3:(F32) | #4:(U32) | #5:(F32) | |:----:|:------:|:---:|:------:|:---:|:---:|:---:|:-----------: |:--: |:--: |:--: |:--: |:--: | |0x2E00| 0x020 | 6 | Rsp | Y | HD | UI | UF Vol. Change Confirmation Response | \ref Data::mAccepted | \ref Data::mReason | \ref Data::mVolume | \ref Data::mDuration | \ref Data::mRate | @param accepted: (int) boolean accept/reject response @param reason: (int) rejection reason @param volume: (float) Ultrafiltration Volume @param duration: (int) Treatment Duration @param rate: (float) Ultrafiltration Rate @return: none """ payload = integer_to_bytearray(accepted) payload += integer_to_bytearray(reason) payload += float_to_bytearray(volume) payload += integer_to_bytearray(duration) payload += float_to_bytearray(rate) message = DenaliMessage.build_message(channel_id=DenaliChannels.hd_to_ui_ch_id, message_id=MsgIds.MSG_ID_USER_UF_SETTINGS_CHANGE_CONFIRMATION_RESPONSE.value, payload=payload) self.can_interface.send(message, 0) def cmd_set_treatment_adjust_ultrafiltration_confirm_rejected(self, reason: int) -> None: """ a convenient method for cmd_set_treatment_adjust_ultrafiltration_confirm_response which only sends a rejection reason and sends other values all as zero @param reason: (int) rejection reason @return: none """ self.cmd_set_treatment_adjust_ultrafiltration_confirm_response(0, reason, 0, 0, 0) def cmd_set_treatment_time(self, sec_total: int, sec_elapsed: int, sec_remain: int = 0) -> None: """ the Treatment Time Data message setter/sender method | MSG | CAN ID | Box | Type | Ack | Src | Dst | Description | #1:(U32) | #2:(U32) | #3:(U32) | |:----:|:------:|:---:|:------:|:---:|:---:|:---:|:-----------: |:--: |:--: |:--: | |0x0D00| 0x040 | 7 | 1 Hz | N | HD | All | Treatment Time Data | \ref Data::mTotal | \ref Data::mElapsed | \ref Data::mRemaining | @param sec_total: (int) Treatment Total Duration in Seconds @param sec_elapsed: (int) Treatment Total Elapsed Time in Seconds @param sec_remain: (int) Treatment Remaining Time in Seconds @return: none """ if sec_remain is None: sec_remain = sec_total - sec_elapsed payload = integer_to_bytearray(sec_total) payload += integer_to_bytearray(sec_elapsed) payload += integer_to_bytearray(sec_remain) message = DenaliMessage.build_message(channel_id=DenaliChannels.hd_sync_broadcast_ch_id, message_id=MsgIds.MSG_ID_TREATMENT_TIME_DATA.value, payload=payload) self.can_interface.send(message, 0) def cmd_set_treatment_ultrafiltration_outlet_flow_data(self, ref_uf_vol: float, measured_uf_vol: float, rot_speed: float, mot_speed: float, mc_speed: float, mc_current: float, pwm: float, corr_offset: float, pump_calculated_rate: float, uf_calculated_rate: float, rotor_hall: int ) -> None: """ the Outlet Flow Data message setter/sender method | MSG | CAN ID | Box | Type | Ack | Src | Dst | Description | #1:(F32) | #2:(F32) | #3:(F32) | #4:(F32) | #5:(F32) | #6:(F32) | #7:(F32) | |:----:|:------:|:---:|:------:|:---:|:---:|:---:|:-----------: |:--: |:--: |:--: |:--: |:--: |:--: |:--: | |0x0B00| 0x040 | 7 | 1 Hz | N | HD | All | Outlet Flow Data | \ref Data::mRefUFVol | \ref Data::mMeasUFVol | \ref Data::mRotorSpeed | \ref Data::mMotorSpeed | \ref Data::mMotorCtlSpeed | \ref Data::mMotorCtlCurrent | \ref Data::mPWMDtCycle | @param ref_uf_vol: (float) Ref UF Volume @param measured_uf_vol: (float) Measured UF Volume @param rot_speed: (float) Rot Speed @param mot_speed: (float) Motor Speed @param mc_speed: (float) MC Speed @param mc_current: (float) MC Current @param pwm: (float) PWM @param corr_offset: correction offset for calculated DPo flow rate. @param pump_calculated_rate: calculated DPo flow rate. @param uf_calculated_rate: calculated UF rate. @param rotor_hall: rotor hall sensor state (1=home, 0=not home) @return: none """ payload = float_to_bytearray(ref_uf_vol) payload += float_to_bytearray(measured_uf_vol) payload += float_to_bytearray(rot_speed) payload += float_to_bytearray(mot_speed) payload += float_to_bytearray(mc_speed) payload += float_to_bytearray(mc_current) payload += float_to_bytearray(pwm) payload += corr_offset payload += pump_calculated_rate payload += uf_calculated_rate payload += rotor_hall message = DenaliMessage.build_message(channel_id=DenaliChannels.hd_sync_broadcast_ch_id, message_id=MsgIds.MSG_ID_DIALYSATE_OUT_FLOW_DATA.value, payload=payload) self.can_interface.send(message, 0) def cmd_set_pressure_occlusion_data(self, arterial_prs: float, venous_prs: float, blood_pump_occlusion: int, pressure_limit_state: int, arterial_min_limt: int, arterial_max_limt: int, venous_min_limit: int, venous_max_limit: int, arterial_long_filtered_pressure: float, venous_long_filtered_pressure: float) -> None: """ the Pressure/Occlusion Data messages setter/sender method | MSG | CAN ID | Box | Type | Ack | Src | Dst | Description | #1:(F32) | #2:(F32) | #3:(U32) | #4:(U32) | #5:(U32) | |:----:|:------:|:---:|:------:|:---:|:---:|:---:|:-----------: |:--: |:--: |:--: |:--: |:--: | |0x0900| 0x040 | 7 | 1 Hz | N | HD | All | PressureOcclusion Data | \ref Data::mArterialPressure | \ref Data::mVenousPressure | \ref Data::mBloodPumpOcclusion | \ref Data::mDialysateInletPumpOcclusion | \ref Data::mDialysateOutletPumpOcclusion | @param arterial_prs: (float) Arterial Pressure @param venous_prs: (float) Venous Pressure @param blood_pump_occlusion: (uint) Blood Pump Occlusion @param : (uint) Current pressure limits state @param : (int) Current arterial minimum pressure limit @param : (int) Current arterial maximum pressure limit @param : (int) Current venous minimum pressure limit @param : (int) Current venous maximum pressure limit @param : (float) Latest long filtered arterial pressure @param : (float) Latest long filtered venous pressure @return: none """ payload = float_to_bytearray(arterial_prs) payload += float_to_bytearray(venous_prs) payload += integer_to_bytearray(blood_pump_occlusion) payload += integer_to_bytearray(pressure_limit_state) payload += integer_to_bytearray(arterial_min_limt) payload += integer_to_bytearray(arterial_max_limt) payload += integer_to_bytearray(venous_min_limit) payload += integer_to_bytearray(venous_max_limit) payload += float_to_bytearray(arterial_long_filtered_pressure) payload += float_to_bytearray(venous_long_filtered_pressure) message = DenaliMessage.build_message(channel_id=DenaliChannels.hd_sync_broadcast_ch_id, message_id=MsgIds.MSG_ID_PRESSURE_OCCLUSION_DATA.value, payload=payload) self.can_interface.send(message, 0) def cmd_set_treatment_states_data(self, sub_mode: int, uf_state: int, saline_state: int, heparin_state: int, rinseback_state: int, recirculate_state: int, blood_prime_state: int, treatment_end_state: int, treatment_stop_state: int, dialysis_state: int) -> None: """ the Treatment States Data message setter/sender method | MSG | CAN ID | Box | Type | Ack | Src | Dst | Description | |:----:|:------:|:---:|:------:|:---:|:---:|:---:|:-----------: | |0x0F00| 0x040 | 7 | 1 Hz | N | HD | All | Treatment States Data | | #1:(U32) | #2:(U32) | #3:(U32) || |:--: |:--: |:--: || | \ref Data::mSubMode | \ref Data::mUFState | \ref Data::mSalineState || |||| | #4:(U32) | #5:(U32) | #6:(U32) || |:--: |:--: |:--: || | \ref Data::mHeparinState | \ref Data::mRinsebackState | \ref Data::mRecirculateState || |||| | #7:(U32) | #8:(U32) | #9:(U32) | |:--: |:--: |:--: | | \ref Data::vBloodPrimeState | \ref Data::mTreatmentEndState | \ref Data::mTreammentStopState | | #9:(U32) || |:--: || | \ref Data::mDialysisState || @param sub_mode: (int) Sub-Mode @param uf_state: (int) UF State @param saline_state: (int) Saline Bolus State @param heparin_state: (int) Saline Bolus State @param rinseback_state: (int) Rinseback State @param recirculate_state: (int) Recirculate State @param blood_prime_state: (int) Blood Prime State @param treatment_end_state: (int) Treatment End State @param treatment_stop_state: (int) Treatment Stop State @param dialysis_state: (int) Dialysis State @return: none """ payload = integer_to_bytearray(sub_mode) payload += integer_to_bytearray(uf_state) payload += integer_to_bytearray(saline_state) payload += integer_to_bytearray(heparin_state) payload += integer_to_bytearray(rinseback_state) payload += integer_to_bytearray(recirculate_state) payload += integer_to_bytearray(blood_prime_state) payload += integer_to_bytearray(treatment_end_state) payload += integer_to_bytearray(treatment_stop_state) payload += integer_to_bytearray(dialysis_state) message = DenaliMessage.build_message(channel_id=DenaliChannels.hd_sync_broadcast_ch_id, message_id=MsgIds.MSG_ID_TREATMENT_STATE_DATA.value, payload=payload) self.can_interface.send(message, 0) def cmd_set_treatment_start_state(self) -> None: """ starting the treatment for user convenience since Tx is not by default running @return: none """ self.cmd_set_treatment_states_data(TXStates.TREATMENT_DIALYSIS_STATE, TXStates.UF_OFF_STATE, # TBD this & follows are Submodes not States as expecte TXStates.SALINE_BOLUS_STATE_IDLE, TXStates.HEPARIN_STATE_OFF, TXStates.RINSEBACK_STOP_INIT_STATE, TXStates.TREATMENT_RECIRC_RECIRC_STATE, TXStates.BLOOD_PRIME_RAMP_STATE, TXStates.TREATMENT_END_WAIT_FOR_RINSEBACK_STATE, TXStates.TREATMENT_STOP_RECIRC_STATE, TXStates.TREATMENT_DIALYSIS_STATE) def cmd_set_hd_operation_mode_data(self, operation_mode: int, operation_sub_mode: int) -> None: """ The HD Operation Mode Data message setter/sender method | MSG | CAN ID | Box | Type | Ack | Src | Dst | Description | #1:(U32) | |:----:|:------:|:---:|:------:|:---:|:---:|:---:|:-----------: |:--: | |0x2500| 0x040 | 7 | 1 Hz | N | HD | All | HD Operation Mode Data | \ref Data::mOpMode | @param operation_mode: (int) Operation Mode @param operation_sub_mode: (int) Operation Sub-Mode @return: None """ payload = integer_to_bytearray(operation_mode) payload += integer_to_bytearray(operation_sub_mode) message = DenaliMessage.build_message(channel_id=DenaliChannels.hd_sync_broadcast_ch_id, message_id=MsgIds.MSG_ID_HD_OP_MODE_DATA.value, payload=payload) self.can_interface.send(message, 0) def cmd_set_treatment_saline_bolus_data(self, target: int, cumulative: float, delivered: float) -> None: """ the Treatment Saline Bolus Data message sender method | MSG | CAN ID | Box | Type | Ack | Src | Dst | Description | #1:(U32) | #2:(F32) | #3:(F32) | |:----:|:------:|:---:|:------:|:---:|:---:|:---:|:-----------: |:--: |:--: |:--: | |0x2F00| 0x040 | 7 | 1 Hz | N | HD | All | Treatment Saline Bolus Data | \ref Data::mTarget | \ref Data::mCumulative | \ref Data::mDelivered | @param target: (int) Saline Bolus Target Volume @param cumulative: (float) Saline Bolus Cumulative Volume @param delivered: (float) Saline Bolus Delivered Volume @return: none """ payload = integer_to_bytearray(target) payload += float_to_bytearray(cumulative) payload += float_to_bytearray(delivered) message = DenaliMessage.build_message(channel_id=DenaliChannels.hd_sync_broadcast_ch_id, message_id=MsgIds.MSG_ID_SALINE_BOLUS_DATA.value, payload=payload) self.can_interface.send(message, 0) def cmd_set_saline_bolus_response(self, accepted: int, reason: int, target: int) -> None: """ the Saline Bolus Response message sender method | MSG | CAN ID | M.Box | Type | Ack | Src | Dest | Description | #1:(U32) | #2:(U32) | #3:(U32) | |:---:|:------:|:-----:|:----:|:---:|:---:|:----:|:---------------------:|:--------------------:|:-------------------:|:-------------------:| | 20 | 0x020 | 6 | Rsp | Y | HD | UI | Saline Bolus Response | \ref Data::mAccepted | \ref Data::mReason | \ref Data::mTarget | @param accepted: (int) boolean accept/reject response @param reason: (int) rejection reason @param target: (int) Saline Bolus Target Volume @param state: (int) Saline Bolus current State @return: none """ payload = integer_to_bytearray(accepted) payload += integer_to_bytearray(reason) payload += integer_to_bytearray(target) message = DenaliMessage.build_message(channel_id=DenaliChannels.hd_to_ui_ch_id, message_id=MsgIds.MSG_ID_USER_SALINE_BOLUS_RESPONSE.value, payload=payload) self.can_interface.send(message, 0) def cmd_set_canbus_fault_count(self, count: int) -> None: """ the CANBus fault count message setter/sender method @param count: (int) Fault Count @return: none """ payload = integer_to_bytearray(count) message = DenaliMessage.build_message(channel_id=DenaliChannels.hd_to_ui_ch_id, message_id=MsgIds.MSG_ID_CAN_ERROR_COUNT.value, payload=payload) self.can_interface.send(message, 0) HDSimulator.wait_for_message_to_be_sent() def cmd_send_unknown_hd(self) -> None: """ the unknown message from HD setter/sender method @return: none """ payload = integer_to_bytearray(0) message = DenaliMessage.build_message(channel_id=DenaliChannels.hd_to_ui_ch_id, message_id=MsgIds.MSG_ID_UNUSED.value, payload=payload) self.can_interface.send(message, 0) def cmd_set_treatment_heparin_data(self, cumulative: float, required: float) -> None: """ the Treatment Heparin Data message setter/sender method | MSG | CAN ID | Box | Type | Ack | Src | Dst | Description | #1:(F32) | #1:(F32) | |:----:|:------:|:---:|:------:|:---:|:---:|:---:|:-----------: |:--: |:--: | |0x4D00| 0x040 | 7 | 1 Hz | N | HD | All | Treatment Heparin Data | \ref Data::mCumulative | \ref Data::mRequired | @param cumulative: (float) Heparin Cumulative Volume @param required: (float) Heparin Volume required for treatment @return: none """ payload = float_to_bytearray(cumulative) payload += float_to_bytearray(required) message = DenaliMessage.build_message(channel_id=DenaliChannels.hd_sync_broadcast_ch_id, message_id=MsgIds.MSG_ID_HD_HEPARIN_DATA.value, payload=payload) self.can_interface.send(message, 0) def cmd_set_heparin_pause_resume_response(self, accepted: int, reason: int) -> None: """ the Heparin Response message setter/sender method | MSG | CAN ID | M.Box | Type | Ack | Src | Dest | Description | #1:(U32) | #2:(U32) | |:----:|:------:|:-----:|:----:|:---:|:---:|:----:|:----------------:|:--------------------:|:-------------------:| |0x4C00| 0x020 | 6 | Rsp | Y | HD | UI | Heparin Response | \ref Data::mAccepted | \ref Data::mReason | @param accepted: (int) boolean accept/reject response @param reason: (int) rejection reason @return: none """ payload = integer_to_bytearray(accepted) payload += integer_to_bytearray(reason) message = DenaliMessage.build_message(channel_id=DenaliChannels.hd_to_ui_ch_id, message_id=MsgIds.MSG_ID_HD_HEPARIN_PAUSE_RESUME_RESPONSE.value, payload=payload) self.can_interface.send(message, 0) def cmd_send_treatment_adjust_pressures_limit_response(self, accepted: int, reason: int, arterial_pressure_limit_window: int, venous_pressure_limit_window: int, venous_pressure_limit_asymmetric: int) -> None: """ the Blood/dialysate rate change Response message setter/sender method | MSG | CAN ID | Box | Type | Ack | Src | Dst | Description | #1:(U32) | #2:(U32) | #3:(S32) | #4:(S32) | #3:(S32) | #4:(S32) | |:----:|:------:|:---:|:------:|:---:|:---:|:---:|:-----------: |:--: |:--: |:--: |:--: |:--: |:--: | |0x4700| 0x020 | 6 | Rsp | Y | HD | UI | A/V BP Limit Change Response | \ref Data::mAccepted | \ref Data::mReason | \ref Data::mArterialLow | \ref Data::mArterialHigh | \ref Data::mVenousLow | \ref Data::mVenousHigh | @param accepted: (int) boolean accept/reject response @param reason: (int) rejection reason @param arterial_pressure_limit_window: (int) Arterial Pressure Limit Low (mmHg) @param venous_pressure_limit_window: (int) Venous Pressure Limit (mmHg) @param venous_pressure_limit_asymmetric: (int) Venous Pressure Asymmetric Limit (mmHg) @return: none """ payload = integer_to_bytearray(accepted) payload += integer_to_bytearray(reason) payload += integer_to_bytearray(arterial_pressure_limit_window) payload += integer_to_bytearray(venous_pressure_limit_window) payload += integer_to_bytearray(venous_pressure_limit_asymmetric) message = DenaliMessage.build_message(channel_id=DenaliChannels.hd_to_ui_ch_id, message_id=MsgIds.MSG_ID_HD_PRESSURE_LIMITS_CHANGE_RESPONSE.value, payload=payload) self.can_interface.send(message, 0) def cmd_send_treatment_adjust_rinseback_response(self, accepted: int, reason: int) -> None: """ the rinseback state change Response message method | MSG | CAN ID | Box | Type | Ack | Src | Dst | Description | #1:(U32) | #2:(U32) | |:----:|:------:|:---:|:------:|:---:|:---:|:---:|:-----------: |:--: |:--: | |0x5300| 0x020 | 6 | Rsp | Y | HD | UI | Rinseback State Change Response | \ref Data::mAccepted | \ref Data::mReason | @param accepted: (int) boolean accept/reject response @param reason : (int) rejection reason @return: None """ payload = integer_to_bytearray(accepted) payload += integer_to_bytearray(reason) message = DenaliMessage.build_message(channel_id=DenaliChannels.hd_to_ui_ch_id, message_id=MsgIds.MSG_ID_HD_RINSEBACK_CMD_RESPONSE.value, payload=payload) self.can_interface.send(message, 0) def cmd_send_treatment_rinseback_data(self, target_vol: float, current_vol: float, flow_rate: int, timeout: int, timeout_countdown: int, is_completed: bool) -> None: """ the rinseback state change Response message method | MSG | CAN ID | Box | Type | Ack | Src | Dst | Description | #1:(F32) | #2:(F32) | #3:(U32) | #4:(U32) | #5:(U32) | #6:(U32) | |:----:|:------:|:---:|:------:|:---:|:---:|:---:|:-----------: |:--: |:--: |:--: |:--: |:--: |:--: | |0x5600| 0x020 | 6 | 1Hz | N | HD | UI | Rinseback progress data | \ref Data::mTarget | \ref Data::mCurrent | \ref Data::mRate | \ref Data::mTimeoutTotal | \ref Data::mTimeoutCountDown | \ref Data::is_completed | @param target_vol : (float) the target volume in mL @param current_vol : (float) the current volume in mL @param flow_rate : (uint ) the current flow rate in mL/min @param timeout : (uint ) Total Timeout @param timeout_countdown : (uint ) Current Timeout count down @param is_completed : (bool ) Is Rinseback completed. @return: None """ payload = float_to_bytearray(target_vol) payload += float_to_bytearray(current_vol) payload += integer_to_bytearray(flow_rate) payload += integer_to_bytearray(timeout) payload += integer_to_bytearray(timeout_countdown) payload += integer_to_bytearray(is_completed) message = DenaliMessage.build_message(channel_id=DenaliChannels.hd_to_ui_ch_id, message_id=MsgIds.MSG_ID_HD_RINSEBACK_PROGRESS.value, payload=payload) self.can_interface.send(message, 0) def cmd_send_treatment_recirculate_data(self, timeout_total: int, timeout_count_down: int) -> None: """ the rinseback state change Response message method | MSG | CAN ID | Box | Type | Ack | Src | Dst | Description | #1:(U32) | #2:(U32) | |:----:|:------:|:---:|:------:|:---:|:---:|:---:|:-----------: |:--: |:--: | |0x5A00| 0x020 | 6 | 1Hz | N | HD | UI | Rinseback progress data | \ref Data::mTimeoutTotal | \ref Data::mTimeoutCountDown | @param timeout_total: (int) Total Timeout @param timeout_count_down: (int) Current Timeout count down @return: None """ payload = integer_to_bytearray(timeout_total) payload += integer_to_bytearray(timeout_count_down) message = DenaliMessage.build_message(channel_id=DenaliChannels.hd_to_ui_ch_id, message_id=MsgIds.MSG_ID_HD_RECIRC_PROGRESS_DATA.value, payload=payload) self.can_interface.send(message, 0) def cmd_send_treatment_blood_prime_data(self, target: float, current: float): """ the bloodprime state change Response message method | MSG | CAN ID | Box | Type | Ack | Src | Dst | Description | #1:(F32) | #2:(F32) | #2:(U32) | |:----:|:------:|:---:|:------:|:---:|:---:|:---:|:-----------: |:--: |:--: |:--: | |0x5900| 0x020 | 6 | 1Hz | N | HD | UI | bloodprime progress data | \ref Data::mTarget | \ref Data::mCurrent | \ref Data::mRate | @param target : (float) the target volume in mL @param current : (float) the current volume in mL @return: None """ payload = float_to_bytearray(target) payload += float_to_bytearray(current) message = DenaliMessage.build_message(channel_id=DenaliChannels.hd_to_ui_ch_id, message_id=MsgIds.MSG_ID_HD_BLOOD_PRIME_PROGRESS_DATA.value, payload=payload) self.can_interface.send(message, 0) def cmd_send_treatment_adjust_recirculate_response(self, accepted: int, reason: int) -> None: """ the recirculate state change Response message method | MSG | CAN ID | Box | Type | Ack | Src | Dst | Description | #1:(U32) | #2:(U32) | |:----:|:------:|:---:|:------:|:---:|:---:|:---:|:-----------: |:--: |:--: | |0x5500| 0x020 | 6 | Rsp | Y | HD | UI | Recirculate State Change Response | \ref Data::mAccepted | \ref Data::mReason | @param accepted: (int) boolean accept/reject response @param reason : (int) rejection reason @return: None """ payload = integer_to_bytearray(accepted) payload += integer_to_bytearray(reason) message = DenaliMessage.build_message(channel_id=DenaliChannels.hd_to_ui_ch_id, message_id=MsgIds.MSG_ID_HD_RECIRC_CMD_RESPONSE.value, payload=payload) self.can_interface.send(message, 0) def _handler_ui_end_treatment(self, message: dict) -> None: """ Handler function when received a request to end a treatment @param message: (dict) the end treatment request @return: None """ self.logger.debug("End treatment requested") request = struct.unpack('i', bytearray( message['message'][MsgFieldPositions.START_POS_FIELD_1:MsgFieldPositions.END_POS_FIELD_1]))[0] if request == 0: self.logger.debug("Request to start rinseback") self.cmd_send_treatment_adjust_end_response(accepted=YES, reason=0) else: self.logger.debug("End treatment unknown request") def cmd_send_treatment_adjust_end_response(self, accepted: int, reason: int): """ the treatment end state change Response message method | MSG | CAN ID | Box | Type | Ack | Src | Dst | Description | #1:(U32) | #2:(U32) | |:----:|:------:|:---:|:------:|:---:|:---:|:---:|:-----------: |:--: |:--: | |0x5800| 0x020 | 6 | Rsp | Y | HD | UI | Treatment End State Change Response | \ref Data::mAccepted | \ref Data::mReason | @param accepted: (int) boolean accept/reject response @param reason: (int) rejection reason @return: None """ payload = integer_to_bytearray(accepted) payload += integer_to_bytearray(reason) message = DenaliMessage.build_message(channel_id=DenaliChannels.hd_to_ui_ch_id, message_id=MsgIds.MSG_ID_HD_TX_END_CMD_RESPONSE.value, payload=payload) self.can_interface.send(message, 0) def cmd_send_accelerometer_hd_data(self, x: float, y: float, z: float, x_max: float, y_max: float, z_max: float, x_tilt: float, y_tilt: float, z_tilt: float) -> None: """ the accelerometer hd data message method | MSG | CAN ID | Box | Type | Ack | Src | Dst | Description | |:----:|:------:|:---:|:------:|:---:|:---:|:---:|:-----------: | |0x3300| 0x040 | 7 | 1Hz | N | HD | UI | HD Accelerometer data | | #1:(F32) | #2:(F32) | #3:(U32) | |:--: |:--: |:--: | | \ref Data::mX | \ref Data::mY | \ref Data::mX | | #4:(F32) | #5:(F32) | #6:(U32) | |:--: |:--: |:--: | | \ref Data::mXMax | \ref Data::mYMax | \ref Data::mXMax | | #7:(F32) | #8:(F32) | #9:(U32) | |:--: |:--: |:--: | | \ref Data::mXTilt | \ref Data::mYTilt | \ref Data::mXTilt | @param x: float - x axis @param y: float - y axis @param z: float - z axis @param x_max: float - x axis max @param y_max: float - y axis max @param z_max: float - z axis max @param x_tilt: float - x axis tilt @param y_tilt: float - y axis tilt @param z_tilt: float - z axis tilt @return: None """ payload = float_to_bytearray(x) payload += float_to_bytearray(y) payload += float_to_bytearray(z) payload += float_to_bytearray(x_max) payload += float_to_bytearray(y_max) payload += float_to_bytearray(z_max) payload += float_to_bytearray(x_tilt) payload += float_to_bytearray(y_tilt) payload += float_to_bytearray(z_tilt) message = DenaliMessage.build_message(channel_id=DenaliChannels.hd_to_ui_ch_id, message_id=MsgIds.MSG_ID_HD_ACCELEROMETER_DATA.value, payload=payload) self.can_interface.send(message, 0) def _handler_request_hd_version(self) -> None: """ Handles a request for the HD version @return: None """ self.logger.debug("Handling request for hd version.") self.cmd_send_version_hd_data(9, 9, 9, 9, 9, 9, 9, 9, 9) self.cmd_send_hd_serial_number() def cmd_send_hd_serial_number(self) -> None: """ Sends the hd serial number response @return: None """ self.logger.debug("Sending hd serial number...") payload = bytearray('0123456789\0', encoding="utf-8") message = DenaliMessage.build_message(channel_id=DenaliChannels.hd_to_ui_ch_id, message_id=MsgIds.MSG_ID_HD_SERIAL_NUMBER.value, payload=payload) self.can_interface.send(message, 0) def cmd_send_version_hd_data(self, major: int, minor: int, micro: int, build: int, fpga_id: int, fpga_major: int, fpga_minor: int, fpga_lab: int, compatibility_rev: int) -> None: """ [0x1D00] # 29 0x040 Rsp Y HD All HD f/w version U08=Major U08=Minor U08=Micro U16=Build U08=FPGA ID U08=FPGA Major U08=FPGA Minor U08=FPGA Lab U32=compatibility rev --- the dg version response message method @param major: (uint) - Major version number @param minor: (uint) - Minor version number @param micro: (uint) - Micro version number @param build: (uint) - Build version number @param fpga_id: (int) - FPGA id version number @param fpga_major: (int) - FPGA Major version number @param fpga_minor: (int) - FPGA Minor version number @param fpga_lab: (int) - FPGA Lab version number @param compatibility_rev: (uint) - The FWs/UI compatibility revision @return: None """ payload = unsigned_byte_to_bytearray(major) payload += unsigned_byte_to_bytearray(minor) payload += unsigned_byte_to_bytearray(micro) payload += unsigned_short_to_bytearray(build) payload += unsigned_byte_to_bytearray(fpga_id) payload += unsigned_byte_to_bytearray(fpga_major) payload += unsigned_byte_to_bytearray(fpga_minor) payload += unsigned_byte_to_bytearray(fpga_lab) payload += unsigned_integer_to_bytearray(compatibility_rev) message = DenaliMessage.build_message(channel_id=DenaliChannels.hd_sync_broadcast_ch_id, message_id=MsgIds.MSG_ID_HD_VERSION.value, payload=payload) self.can_interface.send(message, 0) def cmd_send_serial_hd_data(self, serial: str): """ the hd version serial response message method @param serial: serial number @return: None """ payload = bytes(serial, 'ascii') + b'\x00' message = DenaliMessage.build_message(channel_id=DenaliChannels.hd_to_ui_ch_id, message_id=MsgIds.MSG_ID_HD_SERIAL_NUMBER.value, payload=payload) self.can_interface.send(message, 0) def cmd_send_pre_treatment_state_data(self, sub_mode: int, water_sample_state: int, consumables_self_test_state: int, no_cartridge_self_test_state: int, installation_state: int, dry_self_test_state: int, prime_state: int, recirculate_state: int, patient_connection_state: int, wet_selftests_state: int, pretreatment_rsrvr_state: int ) -> None: """ sends the broadcast message of the pre-treatment states @param sub_mode : (int) the main pre treatment state @param water_sample_state : (int) water sample state @param consumables_self_test_state : (int) consumables self test state @param no_cartridge_self_test_state : (int) no cartridge self-test state @param installation_state : (int) installation state @param dry_self_test_state : (int) dry self-test state @param prime_state : (int) prime state @param recirculate_state : (int) recirculate state @param patient_connection_state : (int) patient connection state @param wet_selftests_state : (int) wet selftest state @param pretreatment_rsrvr_state : (int) reservoir state @return: """ payload = integer_to_bytearray(sub_mode) payload += integer_to_bytearray(water_sample_state) payload += integer_to_bytearray(consumables_self_test_state) payload += integer_to_bytearray(no_cartridge_self_test_state) payload += integer_to_bytearray(installation_state) payload += integer_to_bytearray(dry_self_test_state) payload += integer_to_bytearray(prime_state) payload += integer_to_bytearray(recirculate_state) payload += integer_to_bytearray(patient_connection_state) payload += integer_to_bytearray(wet_selftests_state) payload += integer_to_bytearray(pretreatment_rsrvr_state) message = DenaliMessage.build_message(channel_id=DenaliChannels.hd_sync_broadcast_ch_id, message_id=MsgIds.MSG_ID_PRE_TREATMENT_STATE_DATA.value, payload=payload) self.can_interface.send(message, 0) def cmd_send_pre_treatment_water_sample_response(self, accepted: int, reason: int) -> None: """ send the pretreatment water sample response @param accepted: (int) accept or reject @param reason: (int) rejection reason @return: None """ payload = integer_to_bytearray(accepted) payload += integer_to_bytearray(reason) message = DenaliMessage.build_message(channel_id=DenaliChannels.hd_to_ui_ch_id, message_id=MsgIds.MSG_ID_HD_SAMPLE_WATER_CMD_RESPONSE.value, payload=payload) self.can_interface.send(message, 0) def cmd_send_pre_treatment_self_test_no_cartridge_progress_data(self, timeout: int, countdown: int) -> None: """ send the pretreatment no cartridge self-tests progress data @param timeout: (int) timeout in seconds @param countdown: (int) count down time in second @return: None """ payload = integer_to_bytearray(timeout) payload += integer_to_bytearray(countdown) message = DenaliMessage.build_message(channel_id=DenaliChannels.hd_to_ui_ch_id, message_id=MsgIds.MSG_ID_HD_NO_CART_SELF_TEST_PROGRESS_DATA.value, payload=payload) self.can_interface.send(message, 0) def cmd_send_pre_treatment_self_test_dry_progress_data(self, timeout: int, countdown: int) -> None: """ send the pretreatment dry self-tests progress data @param timeout: (int) timeout time in seconds @param countdown: (int) count down time in second @return: None """ payload = integer_to_bytearray(timeout) payload += integer_to_bytearray(countdown) message = DenaliMessage.build_message(channel_id=DenaliChannels.hd_to_ui_ch_id, message_id=MsgIds.MSG_ID_HD_DRY_SELF_TEST_PROGRESS.value, payload=payload) self.can_interface.send(message, 0) def cmd_send_pre_treatment_disposables_prime_progress_data(self, timeout: int, countdown: int) -> None: """ Broadcasts the progress time data of pre-treatment disposables priming to the UI @param timeout : (int) the total progress time timeout in seconds @param countdown: (int) the remaining time in seconds @return: None """ payload = integer_to_bytearray(timeout) payload += integer_to_bytearray(countdown) message = DenaliMessage.build_message(channel_id=DenaliChannels.hd_sync_broadcast_ch_id, message_id=MsgIds.MSG_ID_HD_PRIMING_STATUS_DATA.value, payload=payload) self.can_interface.send(message, 0) def cmd_send_pre_treatment_prime_start_response(self, accepted: int, reason: int) -> None: """ send the pretreatment prime start response @param accepted: (int) accept or reject @param reason: (int) rejection reason @return: None """ payload = integer_to_bytearray(accepted) payload += integer_to_bytearray(reason) message = DenaliMessage.build_message(channel_id=DenaliChannels.hd_to_ui_ch_id, message_id=MsgIds.MSG_ID_HD_START_PRIME_RESPONSE.value, payload=payload) self.can_interface.send(message, 0) def cmd_send_pre_treatment_continue_to_treament_response(self, accepted: int, reason: int) -> None: """ send the pretreatment continue to treatment response @param accepted: (int) accept or reject @param reason: (int) rejection reason @return: None """ payload = integer_to_bytearray(accepted) payload += integer_to_bytearray(reason) message = DenaliMessage.build_message(channel_id=DenaliChannels.hd_to_ui_ch_id, message_id=MsgIds.MSG_ID_HD_START_TREATMENT_RESPONSE.value, payload=payload) self.can_interface.send(message, 0) def cmd_send_pre_treatment_patient_connection_confirm_response(self, accepted: int, reason: int) -> None: """ send the pretreatment patient connection confirm response @param accepted: (int) accept or reject @param reason: (int) rejection reason @return: None """ payload = integer_to_bytearray(accepted) payload += integer_to_bytearray(reason) message = DenaliMessage.build_message(channel_id=DenaliChannels.hd_to_ui_ch_id, message_id=MsgIds.MSG_ID_HD_PATIENT_CONNECTION_CONFIRM_RESPONSE.value, payload=payload) self.can_interface.send(message, 0) def cmd_send_post_treatment_disposable_removal_confirm_response(self, accepted: int, reason: int) -> None: """ send post treatment disposable removal confirm response @param accepted: (int) accept or reject @param reason: (int) rejection reason @return: None """ payload = integer_to_bytearray(accepted) payload += integer_to_bytearray(reason) message = DenaliMessage.build_message(channel_id=DenaliChannels.hd_to_ui_ch_id, message_id=MsgIds.MSG_ID_HD_DISPOSABLE_REMOVAL_CONFIRM_RESPONSE.value, payload=payload) self.can_interface.send(message, 0) def cmd_send_post_treatment_log_response(self, accepted: bool, reason: int, blood_flow_rate: int, dialysate_flow_rate: int, treatment_duration: int, actual_treatment_duration: int, acid_concentrate_type: int, bicarbonate_concentrate_type: int, potassium_concentration: int, calcium_concentration: int, bicarbonate_concentration: int, sodium_concentration: int, dialysate_temperature: float, dialyzer_type: int, treatment_start_date_time: int, treatment_end_date_time: int, average_blood_flow: float, average_dialysate_flow: float, dialysate_volume_used: float, average_dialysate_temp: float, origin_uf_volume: float, target_uf_volume: float, actual_uf_volume: float, origin_uf_rate: float, target_uf_rate: float, actual_uf_rate: float, saline_bolus_volume: int, heparin_bolus_volume: float, heparin_dispense_rate: float, heparin_pre_stop: int, heparin_delivered_volume: float, heparin_type: int, average_arterial_pressure: float, average_venous_pressure: float, device_id: int, water_sample_test_result: int ) -> None: """ send post treatment log response @param accepted: true if accepted @param reason: the rejection reason @param blood_flow_rate: blood flow rate @param dialysate_flow_rate: dialysate flow rate @param treatment_duration: treatment duration @param actual_treatment_duration: actual treatment duration @param acid_concentrate_type: acid concentrate type @param bicarbonate_concentrate_type: bicarbonate concentrate type @param potassium_concentration: potassium concentration @param calcium_concentration: calcium concentration @param bicarbonate_concentration: bicarbonate concentration @param sodium_concentration: sodium concentration @param dialysate_temperature: dialysate temperature @param dialyzer_type: dialyzer type @param treatment_start_date_time: treatment start date time @param treatment_end_date_time: treatment end date time @param average_blood_flow: average blood flow @param average_dialysate_flow: average dialysate flow @param dialysate_volume_used: dialysate volume used @param average_dialysate_temp: average dialysate temp @param origin_uf_volume: origin uf volume @param target_uf_volume: target uf volume @param actual_uf_volume: actual uf volume @param origin_uf_rate: origin uf rate @param target_uf_rate: target uf rate @param actual_uf_rate: actual uf rate @param saline_bolus_volume: saline bolus volume @param heparin_bolus_volume: heparin bolus volume @param heparin_dispense_rate: heparin dispense rate @param heparin_pre_stop: heparin pre stop @param heparin_delivered_volume: heparin delivered volume @param heparin_type: heparin type @param average_arterial_pressure: average arterial pressure @param average_venous_pressure: average venous pressure @param device_id: device id @param water_sample_test_result: water sample test result @return: None """ payload = integer_to_bytearray(accepted) payload += integer_to_bytearray(reason) payload += unsigned_integer_to_bytearray(int(blood_flow_rate)) payload += unsigned_integer_to_bytearray(int(dialysate_flow_rate)) payload += unsigned_integer_to_bytearray(int(treatment_duration)) payload += unsigned_integer_to_bytearray(int(actual_treatment_duration)) payload += unsigned_integer_to_bytearray(int(acid_concentrate_type)) payload += unsigned_integer_to_bytearray(int(bicarbonate_concentrate_type)) payload += unsigned_integer_to_bytearray(int(potassium_concentration)) payload += unsigned_integer_to_bytearray(int(calcium_concentration)) payload += unsigned_integer_to_bytearray(int(bicarbonate_concentration)) payload += unsigned_integer_to_bytearray(int(sodium_concentration)) payload += float_to_bytearray(float(dialysate_temperature)) payload += unsigned_integer_to_bytearray(int(dialyzer_type)) payload += unsigned_integer_to_bytearray(int(treatment_start_date_time)) payload += unsigned_integer_to_bytearray(int(treatment_end_date_time)) payload += float_to_bytearray(float(average_blood_flow)) payload += float_to_bytearray(float(average_dialysate_flow)) payload += float_to_bytearray(float(dialysate_volume_used)) payload += float_to_bytearray(float(average_dialysate_temp)) payload += float_to_bytearray(float(origin_uf_volume)) payload += float_to_bytearray(float(target_uf_volume)) payload += float_to_bytearray(float(actual_uf_volume)) payload += float_to_bytearray(float(origin_uf_rate)) payload += float_to_bytearray(float(target_uf_rate)) payload += float_to_bytearray(float(actual_uf_rate)) payload += unsigned_integer_to_bytearray(int(saline_bolus_volume)) payload += float_to_bytearray(float(heparin_bolus_volume)) payload += float_to_bytearray(float(heparin_dispense_rate)) payload += unsigned_integer_to_bytearray(int(heparin_pre_stop)) payload += float_to_bytearray(float(heparin_delivered_volume)) payload += unsigned_integer_to_bytearray(int(heparin_type)) payload += float_to_bytearray(float(average_arterial_pressure)) payload += float_to_bytearray(float(average_venous_pressure)) payload += unsigned_integer_to_bytearray(int(device_id)) payload += unsigned_integer_to_bytearray(int(water_sample_test_result)) message = DenaliMessage.build_message(channel_id=DenaliChannels.hd_to_ui_ch_id, message_id=MsgIds.MSG_ID_HD_TREATMENT_LOG_DATA_RESPONSE.value, payload=payload) self.can_interface.send(message, 0) def cmd_send_treatment_log_data(self, blood_flow_rate: int, dialysate_flow_rate: int, uf_rate: float, arterial_pressure: float, venous_pressure: float) -> None: """ send the treatment log data @param blood_flow_rate: (int) blood flow rate @param dialysate_flow_rate: (int) Dialysate Flow Rate @param uf_rate: (float) UF Rate @param arterial_pressure: (float) Arterial Pressure @param venous_pressure: (float) Venous Pressure @return: None """ payload = integer_to_bytearray(blood_flow_rate) payload += integer_to_bytearray(dialysate_flow_rate) payload += float_to_bytearray(uf_rate) payload += float_to_bytearray(arterial_pressure) payload += float_to_bytearray(venous_pressure) message = DenaliMessage.build_message(channel_id=DenaliChannels.hd_to_ui_ch_id, message_id=MsgIds.MSG_ID_HD_TREATMENT_LOG_PERIODIC_DATA.value, payload=payload) self.can_interface.send(message, 0) def cmd_send_treatment_log_alarm(self, alarm_id: int, parameter1: float, parameter2: float) -> None: """ send the treatment log data @param alarm_id: (U32) alarm ID @param parameter1: (F32) paramter 1 (it's not clear yet how many paramters with what type is required and this is only plceholder) @param parameter2: (F32) paramter 2 (it's not clear yet how many paramters with what type is required and this is only plceholder) @return: None """ payload = integer_to_bytearray(alarm_id) payload += float_to_bytearray(parameter1) payload += float_to_bytearray(parameter2) message = DenaliMessage.build_message(channel_id=DenaliChannels.hd_to_ui_ch_id, message_id=MsgIds.MSG_ID_HD_TREATMENT_LOG_ALARM_EVENT.value, payload=payload) self.can_interface.send(message, 0) def cmd_send_treatment_log_event(self, event_id: int, old_value: float, new_value: float) -> None: """ send the treatment log data @param event_id: (U32) alarm ID @param old_value: (F32) the old value @param new_value: (F32) the new value @return: none """ payload = integer_to_bytearray(event_id) payload += float_to_bytearray(old_value) payload += float_to_bytearray(new_value) message = DenaliMessage.build_message(channel_id=DenaliChannels.hd_to_ui_ch_id, message_id=MsgIds.MSG_ID_HD_TREATMENT_LOG_EVENT.value, payload=payload) self.can_interface.send(message, 0) def cmd_send_hd_disinfection_state(self, sub_mode: int, flush_mode: int, heat_mode: int, chemical_mode: int) -> None: """ Broadcasts the current DG disinfection mode @param sub_mode: (int) disinfect states @param flush_mode: (int) flush states @param heat_mode: (int) heat states @param chemical_mode: (int) chemical states @return: None """ payload = integer_to_bytearray(sub_mode) payload += integer_to_bytearray(flush_mode) payload += integer_to_bytearray(heat_mode) payload += integer_to_bytearray(chemical_mode) message = DenaliMessage.build_message(channel_id=DenaliChannels.hd_sync_broadcast_ch_id, message_id=MsgIds.MSG_ID_HD_DISINFECT_STANDBY_DATA.value, payload=payload) self.can_interface.send(message, 0) def cmd_send_hd_disinfect_response(self, accepted: bool, reason: int) -> None: """ the HD response to the request from UI to initiate a disinfection/flush @param accepted: boolean accepted or rejected @param reason: the rejection reason @return: None """ payload = integer_to_bytearray(accepted) payload += integer_to_bytearray(reason) message = DenaliMessage.build_message(channel_id=DenaliChannels.hd_to_ui_ch_id, message_id=MsgIds.MSG_ID_HD_DISINFECT_RESPONSE.value, payload=payload) self.can_interface.send(message, 0) def cmd_send_hd_disinfect_chemical_confirm(self, accepted: bool, reason: int) -> None: """ the HD response to the UI sending the user chemical disinfection steps confirm. @param accepted: boolean accepted or rejected @param reason: the rejection reason @return: None """ payload = integer_to_bytearray(accepted) payload += integer_to_bytearray(reason) message = DenaliMessage.build_message(channel_id=DenaliChannels.hd_to_ui_ch_id, message_id=MsgIds.MSG_ID_HD_CHEM_DISINFECT_CONFIRM_RESPONSE.value, payload=payload) self.can_interface.send(message, 0) @publish(["ui_version"]) def _handler_ui_version(self, message) -> None: """ Handles the ui version response @param message: The ui version response message @return: None """ payload = message['message'] index = DenaliMessage.PAYLOAD_START_INDEX major, index = bytearray_to_byte(payload, index, False) minor, index = bytearray_to_byte(payload, index, False) micro, index = bytearray_to_byte(payload, index, False) build, index = bytearray_to_short(payload, index, False) compt, index = bytearray_to_integer(payload, index, False) self.ui_version = f"v{major}.{minor}.{micro}.{build}.{compt}" self.logger.debug(f"UI VERSION: {self.ui_version}") if self.console_out: print("Version:", self.ui_version) def cmd_send_hd_request_ui_version(self) -> None: """ send HD request for UI version @return: None """ message = DenaliMessage.build_message(channel_id=DenaliChannels.hd_to_ui_ch_id, message_id=MsgIds.MSG_ID_HD_UI_VERSION_INFO_REQUEST.value, payload=None) self.can_interface.send(message, 0) def cmd_send_hd_post(self, item: int, passed: bool, done: bool = False) -> None: """ send HD post message the single(item) or the final(done) @param item: the post state/item index @param passed: the post result single or final @param done: if this is the final post message this should be true @return: None """ payload = integer_to_bytearray(passed) if not done: payload += integer_to_bytearray(item) message = DenaliMessage.build_message(channel_id=DenaliChannels.hd_sync_broadcast_ch_id, message_id=MsgIds.MSG_ID_HD_POST_FINAL_TEST_RESULT.value if done else MsgIds.MSG_ID_HD_POST_SINGLE_TEST_RESULT.value, payload=payload) self.can_interface.send(message, 0) # ------------------------------------------------ GENERAL MESSAGES ------------------------------------------------ def cmd_send_hd_general_response(self, message_id: int, accepted: int, reason: int, is_pure_data: bool = False, has_parameters: bool = False, parameters_payload: any = 0x00, channel_id=DenaliChannels.hd_to_ui_ch_id) -> None: """ a general method to send any standard response message, by it's id and list of parameters. @param message_id: the id of the message @param accepted: the standard accepted parameter of any response message @param reason: the standard rejection reason parameter of any response message @param is_pure_data: The message only has data @param has_parameters: if the message has parameter this needs to be true. @param parameters_payload: the list of parameters pre-converted and ready to be concatenated to the payload. @param channel_id: (int) indicates the channel @return: None """ payload = "" if not is_pure_data: payload = integer_to_bytearray(accepted) payload += integer_to_bytearray(reason) if has_parameters: payload = parameters_payload message = DenaliMessage.build_message(channel_id=channel_id, message_id=message_id, payload=payload) self.can_interface.send(message, 0) def cmd_send_hd_general_progress_data(self, message_id: int, total: int, countdown: int) -> None: """ a general method t send any standard progress data message, by it's id @param message_id: the id of the message @param total: the total value of the progress data @param countdown: the remaining or countdown value @return: None """ payload = integer_to_bytearray(total) payload += integer_to_bytearray(countdown) message = DenaliMessage.build_message(channel_id=DenaliChannels.hd_to_ui_ch_id, message_id=message_id, payload=payload) self.can_interface.send(message, 0) def cmd_send_hd_ack(self, seq: int) -> None: """ sending hd ack message by the sequence seq @param seq: the message sequence number @return: None """ message = DenaliMessage.build_message(channel_id=DenaliChannels.hd_to_ui_ch_id, message_id=GuiActionType.Acknow, seq=seq) self.can_interface.send(message, 0) def cmd_send_power_on_self_test_version_request(self) -> None: """ Sends the power on self test version request @return: None """ payload = bytearray() message = DenaliMessage.build_message(channel_id=DenaliChannels.hd_to_ui_ch_id, message_id=MsgIdsDialin.MSG_DIALIN_ID_HD_VERSION_REQUEST.value, payload=payload) self.can_interface.send(message, 0) def _handler_ui_post_ui_version_compatibility(self, message: dict) -> None: """ Handles the UI's reporting of its version during the power on self tests @param message: The message data @return: None """ ui_major = struct.unpack('i', bytearray( message['message'][MsgFieldPositions.START_POS_FIELD_1:MsgFieldPositions.END_POS_FIELD_1]))[0] ui_minor = struct.unpack('i', bytearray( message['message'][MsgFieldPositions.START_POS_FIELD_2:MsgFieldPositions.END_POS_FIELD_2]))[0] ui_micro = struct.unpack('i', bytearray( message['message'][MsgFieldPositions.START_POS_FIELD_3:MsgFieldPositions.END_POS_FIELD_3]))[0] ui_build = struct.unpack('i', bytearray( message['message'][MsgFieldPositions.START_POS_FIELD_4:MsgFieldPositions.END_POS_FIELD_4]))[0] ui_compatibility = struct.unpack('i', bytearray( message['message'][MsgFieldPositions.START_POS_FIELD_5:MsgFieldPositions.END_POS_FIELD_5]))[0] self.logger.debug("UI version power on self test received: " "Major: {0} " "Minor: {1} " "Micro: {2} " "Build: {3} " "Compat: {4} " .format(ui_major, ui_minor, ui_micro, ui_build, ui_compatibility)) def cmd_send_hd_blood_leak_data(self, blood_leak_status: int, blood_leak_state: int, blood_leak_zero_status_counter: int, blood_leak_counter: int, blood_leak_zeroed_status: int, blood_leak_detect_set_point: int, blood_leak_detect_level: int, blood_leak_st_count: int, blood_leak_led_intensity: int, blood_leak_register_counter: int) -> None: """ A simulated HD broadcast message of blood leak data. @param blood_leak_status: (int) Blood leak status @param blood_leak_state: (int) Blood leak state @param blood_leak_zero_status_counter: (int) Blood leak zero status counter @param blood_leak_counter: (int) Blood leak counter @param blood_leak_zeroed_status: (int) Blood leak zeroed status @param blood_leak_detect_set_point: (int) Blood leak detect set point @param blood_leak_detect_level: (int) Blood leak detect level @param blood_leak_st_count: (int) Blood leak st count @param blood_leak_led_intensity: (int) Blood leak LED intensity @param blood_leak_register_counter: (int) Blood leak register counter @return: None """ payload = unsigned_integer_to_bytearray(blood_leak_status) payload += unsigned_integer_to_bytearray(blood_leak_state) payload += unsigned_integer_to_bytearray(blood_leak_zero_status_counter) payload += unsigned_integer_to_bytearray(blood_leak_counter) payload += unsigned_integer_to_bytearray(blood_leak_zeroed_status) payload += unsigned_integer_to_bytearray(blood_leak_detect_set_point) payload += unsigned_integer_to_bytearray(blood_leak_detect_level) payload += unsigned_integer_to_bytearray(blood_leak_st_count) payload += unsigned_integer_to_bytearray(blood_leak_led_intensity) payload += unsigned_integer_to_bytearray(blood_leak_register_counter) message = DenaliMessage.build_message(channel_id=DenaliChannels.hd_sync_broadcast_ch_id, message_id=MsgIds.MSG_ID_HD_BLOOD_LEAK_DATA.value, payload=payload) self.can_interface.send(message, 0) def cmd_send_hd_air_trap_data(self, lower_level: int, upper_level: int) -> None: """ A simulated HD broadcast message of air trap data. @param lower_level: (int) Air trap lower level @param upper_level: (int) Air trap upper level @return: None """ payload = unsigned_integer_to_bytearray(lower_level) payload += unsigned_integer_to_bytearray(upper_level) message = DenaliMessage.build_message(channel_id=DenaliChannels.hd_sync_broadcast_ch_id, message_id=MsgIds.MSG_ID_HD_AIR_TRAP_DATA.value, payload=payload) self.can_interface.send(message, 0) def cmd_send_hd_air_bubble_data(self, venous_air_bubble_status: int, venous_air_bubble_state: int) -> None: """ A simulated HD broadcast message of air bubble data. @param venous_air_bubble_status: (int) Venous air bubble status @param venous_air_bubble_state: (int) Venous air bubble state @return: None """ payload = unsigned_integer_to_bytearray(venous_air_bubble_status) payload += unsigned_integer_to_bytearray(venous_air_bubble_state) message = DenaliMessage.build_message(channel_id=DenaliChannels.hd_sync_broadcast_ch_id, message_id=MsgIds.MSG_ID_HD_BUBBLES_DATA.value, payload=payload) self.can_interface.send(message, 0)