########################################################################### # # Copyright (c) 2019-2020 Diality Inc. - All Rights Reserved. # # THIS CODE MAY NOT BE COPIED OR REPRODUCED IN ANY FORM, IN PART OR IN # WHOLE, WITHOUT THE EXPLICIT PERMISSION OF THE COPYRIGHT OWNER. # # @file ui.py # # @date 31-Mar-2020 # @author P. Lucia # # @brief # # ############################################################################ from ..protocols.CAN import (DenaliMessage, DenaliChannels) from ..utils.conversions import integer_to_bytearray, float_to_bytearray import struct class HDUIProxy: """ \class HDUIProxy \brief Hemodialysis Device (HD) Dialin API sub-class for ui commands. """ # ui message IDs MSG_ID_UI_CHECKIN_WITH_HD = 0x0007 MSG_ID_HD_UF_PAUSE_RESUME_REQUEST = 0x0010 MSG_ID_HD_TREATMENT_PARAMS_RANGES = 0x001A MSG_ID_UF_SETTINGS_CHANGE_REQUEST_BY_USER = 0x0011 MSG_ID_UF_SETTINGS_CHANGE_RESPONSE_FROM_HD = 0x0013 MSG_ID_UF_SETTINGS_CHANGE_CONFIRMED_BY_USER = 0x0015 MSG_ID_TREATMENT_DURATION_SETTING_CHANGE_REQUEST = 0x0016 MSG_ID_TREATMENT_DURATION_SETTING_CHANGE_RESPONSE_FROM_HD = 0x001B MSG_ID_BLOOD_DIALYSATE_FLOW_SETTING_CHANGE_REQUEST_BY_USER = 0x0017 MSG_ID_BLOOD_DIALYSATE_FLOW_SETTING_CHANGE_RESPONSE_FROM_HD = 0x0018 MSG_ID_UI_REQUEST_HD_VERSION = 0x001C MSG_ID_UI_HD_VERSION_RESPONSE = 0x001D LITER_TO_ML_CONVERSION_FACTOR = 1000.0 UF_CMD_PAUSE = 0 UF_CMD_RESUME = 1 UF_CMD_CHANGE_TIME_TO_ADJUST = 0 UF_CMD_CHANGE_RATE_TO_ADJUST = 1 RESPONSE_REJECTED = 0 RESPONSE_ACCEPTED = 1 # HD 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_BUILD = END_POS_MINOR END_POS_BUILD = START_POS_BUILD + 2 # HD update on valid treatment parameter ranges message field positions START_POS_MIN_TREAT_TIME = DenaliMessage.PAYLOAD_START_INDEX END_POS_MIN_TREAT_TIME = START_POS_MIN_TREAT_TIME + 4 START_POS_MAX_TREAT_TIME = END_POS_MIN_TREAT_TIME END_POS_MAX_TREAT_TIME = START_POS_MAX_TREAT_TIME + 4 START_POS_MIN_UF_VOL = END_POS_MAX_TREAT_TIME END_POS_MIN_UF_VOL = START_POS_MIN_UF_VOL + 4 START_POS_MAX_UF_VOL = END_POS_MIN_UF_VOL END_POS_MAX_UF_VOL = START_POS_MAX_UF_VOL + 4 START_POS_MIN_DIAL_RATE = END_POS_MAX_UF_VOL END_POS_MIN_DIAL_RATE = START_POS_MIN_DIAL_RATE + 4 START_POS_MAX_DIAL_RATE = END_POS_MIN_DIAL_RATE END_POS_MAX_DIAL_RATE = START_POS_MAX_DIAL_RATE + 4 # HD response to treatment duration change request message field positions START_POS_TIME_CHG_RSP_ACCEPTED = DenaliMessage.PAYLOAD_START_INDEX END_POS_TIME_CHG_RSP_ACCEPTED = START_POS_TIME_CHG_RSP_ACCEPTED + 4 START_POS_TIME_CHG_RSP_REASON = END_POS_TIME_CHG_RSP_ACCEPTED END_POS_TIME_CHG_RSP_REASON = START_POS_TIME_CHG_RSP_REASON + 4 START_POS_TIME_CHG_RSP_TIME = END_POS_TIME_CHG_RSP_REASON END_POS_TIME_CHG_RSP_TIME = START_POS_TIME_CHG_RSP_TIME + 4 START_POS_TIME_CHG_RSP_UF_VOL = END_POS_TIME_CHG_RSP_TIME END_POS_TIME_CHG_RSP_UF_VOL = START_POS_TIME_CHG_RSP_UF_VOL + 4 # HD response to UF volume change request message field positions START_POS_UF_CHG_RSP_RESP = DenaliMessage.PAYLOAD_START_INDEX END_POS_UF_CHG_RSP_RESP = START_POS_UF_CHG_RSP_RESP + 4 START_POS_UF_CHG_RSP_REJECT_REASON = END_POS_UF_CHG_RSP_RESP END_POS_UF_CHG_RSP_REJECT_REASON = START_POS_UF_CHG_RSP_REJECT_REASON + 4 START_POS_UF_CHG_RSP_VOL = END_POS_UF_CHG_RSP_RESP END_POS_UF_CHG_RSP_VOL = START_POS_UF_CHG_RSP_VOL + 4 START_POS_UF_CHG_RSP_TIME = END_POS_UF_CHG_RSP_VOL END_POS_UF_CHG_RSP_TIME = START_POS_UF_CHG_RSP_TIME + 4 START_POS_UF_CHG_RSP_TIME_DIFF = END_POS_UF_CHG_RSP_TIME END_POS_UF_CHG_RSP_TIME_DIFF = START_POS_UF_CHG_RSP_TIME_DIFF + 4 START_POS_UF_CHG_RSP_RATE = END_POS_UF_CHG_RSP_TIME_DIFF END_POS_UF_CHG_RSP_RATE = START_POS_UF_CHG_RSP_RATE + 4 START_POS_UF_CHG_RSP_RATE_DIFF = END_POS_UF_CHG_RSP_RATE END_POS_UF_CHG_RSP_RATE_DIFF = START_POS_UF_CHG_RSP_RATE_DIFF + 4 # HD response to blood/dialysate flow change request message field positions START_POS_BLD_DIAL_CHG_RSP_ACCEPTED = DenaliMessage.PAYLOAD_START_INDEX END_POS_BLD_DIAL_CHG_RSP_ACCEPTED = START_POS_BLD_DIAL_CHG_RSP_ACCEPTED + 4 START_POS_BLD_DIAL_CHG_RSP_REASON = END_POS_BLD_DIAL_CHG_RSP_ACCEPTED END_POS_BLD_DIAL_CHG_RSP_REASON = START_POS_BLD_DIAL_CHG_RSP_REASON + 4 START_POS_BLD_DIAL_CHG_RSP_BLD_RATE = END_POS_BLD_DIAL_CHG_RSP_REASON END_POS_BLD_DIAL_CHG_RSP_BLD_RATE = START_POS_BLD_DIAL_CHG_RSP_BLD_RATE + 4 START_POS_BLD_DIAL_CHG_RSP_DIAL_RATE = END_POS_BLD_DIAL_CHG_RSP_BLD_RATE END_POS_BLD_DIAL_CHG_RSP_DIAL_RATE = START_POS_BLD_DIAL_CHG_RSP_DIAL_RATE + 4 def __init__(self, can_interface): """ HD_UI constructor \param can_interface: the denali can interface object """ self.can_interface = can_interface # register function to handle HD response to UF change requests if self.can_interface is not None: channel_id = DenaliChannels.hd_to_ui_ch_id self.can_interface.register_receiving_publication_function(channel_id, self.MSG_ID_UF_SETTINGS_CHANGE_RESPONSE_FROM_HD, self.handler_uf_change_response) self.can_interface.register_receiving_publication_function(channel_id, self.MSG_ID_TREATMENT_DURATION_SETTING_CHANGE_RESPONSE_FROM_HD, self.handler_treatment_duration_change_response) self.can_interface.register_receiving_publication_function(channel_id, self.MSG_ID_BLOOD_DIALYSATE_FLOW_SETTING_CHANGE_RESPONSE_FROM_HD, self.handler_blood_and_dialysate_change_response) self.can_interface.register_receiving_publication_function(channel_id, self.MSG_ID_HD_TREATMENT_PARAMS_RANGES, self.handler_treatment_param_ranges) self.can_interface.register_receiving_publication_function(DenaliChannels.hd_to_ui_ch_id, self.MSG_ID_UI_HD_VERSION_RESPONSE, self.handler_hd_version) # initialize variables that will be populated by HD version response self.hd_version = '' # initialize variables that will be populated by treatment parameter ranges message self.min_treatment_duration_min = 0 self.max_treatment_duration_min = 0 self.min_uf_volume_ml = 0 self.max_uf_volume_ml = 0 self.min_dialysate_flow_rate_ml_min = 0 self.max_dialysate_flow_rate_ml_min = 0 # initialize variables that will be populated by response from HD to treatment duration change request self.duration_change_succeeded = False self.duration_change_reject_reason = 0 self.duration_change_time_min = 0 self.duration_change_uf_vol_ml = 0 # initialize variables that will be populated by response from HD to UF change request self.uf_change_succeeded = False self.uf_change_reject_reason = 0 self.uf_change_volume_ml = 0 self.uf_change_time_min = 0 self.uf_change_rate_ml_min = 0.0 self.uf_change_time_diff = 0 self.uf_change_rate_diff = 0.0 # initialize variables that will be populated by response from HD to blood & dialysate flow rate change request self.blood_and_dialysate_flow_rate_change_succeeded = False self.blood_and_dialysate_flow_rate_change_reject_reason = 0 self.target_blood_flow_rate = 0 self.target_dialysate_flow_rate = 0 def handler_hd_version(self, message): """ Handler for response from HD regarding its version. \param message: response message from HD regarding valid treatment parameter ranges.\n U08 Major \n U08 Minor \n U16 Build \n \returns none """ major = struct.unpack('B', bytearray( message['message'][self.START_POS_MAJOR:self.END_POS_MAJOR])) minor = struct.unpack('B', bytearray( message['message'][self.START_POS_MINOR:self.END_POS_MINOR])) build = struct.unpack('H', bytearray( message['message'][self.START_POS_BUILD:self.END_POS_BUILD])) self.hd_version = 'v'+str(major)+'.'+str(minor)+'.'+str(build) def handler_treatment_param_ranges(self, message): """ Handler for response from HD regarding valid treatment parameter ranges. \param message: response message from HD regarding valid treatment parameter ranges.\n U32 Minimum treatment duration setting (in min.). \n U32 Maximum treatment duration setting (in min.). \n U32 Minimum ultrafiltration volume (in mL). \n U32 Maximum ultrafiltration volume (in mL). \n U32 Minimum dialysate flow rate (in mL/min). \n U32 Maximum dialysate flow rate (in mL/min). \returns none """ mintime = struct.unpack('i', bytearray( message['message'][self.START_POS_MIN_TREAT_TIME:self.END_POS_MIN_TREAT_TIME])) maxtime = struct.unpack('i', bytearray( message['message'][self.START_POS_MAX_TREAT_TIME:self.END_POS_MAX_TREAT_TIME])) minufvol = struct.unpack('i', bytearray( message['message'][self.START_POS_MIN_UF_VOL:self.END_POS_MIN_UF_VOL])) maxufvol = struct.unpack('i', bytearray( message['message'][self.START_POS_MAX_UF_VOL:self.END_POS_MAX_UF_VOL])) mindialrt = struct.unpack('i', bytearray( message['message'][self.START_POS_MIN_DIAL_RATE:self.END_POS_MIN_DIAL_RATE])) maxdialrt = struct.unpack('i', bytearray( message['message'][self.START_POS_MAX_DIAL_RATE:self.END_POS_MAX_DIAL_RATE])) self.min_treatment_duration_min = mintime[0] self.max_treatment_duration_min = maxtime[0] self.min_uf_volume_ml = minufvol[0] self.max_uf_volume_ml = maxufvol[0] self.min_dialysate_flow_rate_ml_min = mindialrt[0] self.max_dialysate_flow_rate_ml_min = maxdialrt[0] def handler_treatment_duration_change_response(self, message): """ Handler for response from HD regarding treatment duration change request. \param message: response message from HD regarding treatment duration change.\n BOOL Accepted \n U32 Reject reason (if not accepted) \n U32 treatment duration (min) \n U32 UF volue (mL) \n \returns none """ rsp = struct.unpack('i', bytearray( message['message'][self.START_POS_TIME_CHG_RSP_ACCEPTED:self.END_POS_TIME_CHG_RSP_ACCEPTED])) rea = struct.unpack('i', bytearray( message['message'][self.START_POS_TIME_CHG_RSP_REASON:self.END_POS_TIME_CHG_RSP_REASON])) tim = struct.unpack('i', bytearray( message['message'][self.START_POS_TIME_CHG_RSP_TIME:self.END_POS_TIME_CHG_RSP_TIME])) vol = struct.unpack('i', bytearray( message['message'][self.START_POS_TIME_CHG_RSP_UF_VOL:self.END_POS_TIME_CHG_RSP_UF_VOL])) self.duration_change_succeeded = rsp[0] self.duration_change_reject_reason = rea[0] self.duration_change_time_min = tim[0] self.duration_change_uf_vol_ml = vol[0] def handler_blood_and_dialysate_change_response(self, message): """ Handler for response from HD regarding blood & dialysate flow rate change request. \param message: response message from HD regarding requested blood & dialysate flow rate settings change.\n BOOL Accepted \n U32 Reject reason (if not accepted) \n U32 Blood flow rate (mL/min) \n U32 Dialysate flow rate (mL/min) \n \returns none """ rsp = struct.unpack('i', bytearray( message['message'][self.START_POS_BLD_DIAL_CHG_RSP_ACCEPTED:self.END_POS_BLD_DIAL_CHG_RSP_ACCEPTED])) rea = struct.unpack('i', bytearray( message['message'][self.START_POS_BLD_DIAL_CHG_RSP_REASON:self.END_POS_BLD_DIAL_CHG_RSP_REASON])) bld = struct.unpack('i', bytearray( message['message'][self.START_POS_BLD_DIAL_CHG_RSP_BLD_RATE:self.END_POS_BLD_DIAL_CHG_RSP_BLD_RATE])) dil = struct.unpack('i', bytearray( message['message'][self.START_POS_BLD_DIAL_CHG_RSP_DIAL_RATE:self.END_POS_BLD_DIAL_CHG_RSP_DIAL_RATE])) if rsp[0] == self.RESPONSE_REJECTED: resp = False else: resp = True self.blood_and_dialysate_flow_rate_change_succeeded = resp self.blood_and_dialysate_flow_rate_change_reject_reason = rea[0] self.target_blood_flow_rate = bld[0] self.target_dialysate_flow_rate = dil[0] def handler_uf_change_response(self, message): """ Handler for response from HD regarding UF change request. \param message: response message from HD regarding requested ultrafiltration settings change.\n BOOL Accepted \n U32 RejectReason (if not accepted) F32 UF Volume (mL) - converted to Liters \n U32 treatment Time (min) \n S32 treatment Time Change (min) \n F32 UF Rate (mL/min) \n F32 UF Rate Change (mL/min) \returns none """ rsp = struct.unpack('i', bytearray( message['message'][self.START_POS_UF_CHG_RSP_RESP:self.END_POS_UF_CHG_RSP_RESP])) rea = struct.unpack('i', bytearray( message['message'][self.START_POS_UF_CHG_RSP_REJECT_REASON:self.END_POS_UF_CHG_RSP_REJECT_REASON])) vol = struct.unpack('f', bytearray( message['message'][self.START_POS_UF_CHG_RSP_VOL:self.END_POS_UF_CHG_RSP_VOL])) tim = struct.unpack('i', bytearray( message['message'][self.START_POS_UF_CHG_RSP_TIME:self.END_POS_UF_CHG_RSP_TIME])) tmd = struct.unpack('i', bytearray( message['message'][self.START_POS_UF_CHG_RSP_TIME_DIFF:self.END_POS_UF_CHG_RSP_TIME_DIFF])) rat = struct.unpack('f', bytearray( message['message'][self.START_POS_UF_CHG_RSP_RATE:self.END_POS_UF_CHG_RSP_RATE])) rtd = struct.unpack('f', bytearray( message['message'][self.START_POS_UF_CHG_RSP_RATE_DIFF:self.END_POS_UF_CHG_RSP_RATE_DIFF])) if rsp[0] == self.RESPONSE_REJECTED: resp = False else: resp = True self.uf_change_succeeded = resp self.uf_change_reject_reason = rea[0] self.UFChangeVolumeL = vol[0] / self.LITER_TO_ML_CONVERSION_FACTOR self.uf_change_time_min = tim[0] self.uf_change_time_diff = tmd[0] self.uf_change_rate_ml_min = rat[0] self.UFChangeRateDiff = rtd[0] def cmd_ui_checkin_with_hd(self): """ Constructs and sends the ui check-in message \returns 0 """ message = DenaliMessage.build_message(channel_id=DenaliChannels.dialin_to_hd_ch_id, message_id=self.MSG_ID_UI_CHECKIN_WITH_HD) print("Sending ui checkin w/ HD") self.can_interface.send(message, 0) return 0 def cmd_ui_request_hd_version(self): """ Constructs and sends the ui request for version message """ message = DenaliMessage.build_message(channel_id=DenaliChannels.dialin_to_hd_ch_id, message_id=self.MSG_ID_UI_REQUEST_HD_VERSION) print("Sending ui request for version to HD") self.can_interface.send(message, 0) def cmd_ui_uf_pause_resume(self, cmd=UF_CMD_PAUSE): """ Constructs and sends a ui UF command message \param cmd: 0 for pause, 1 for resume \returns none """ payload = integer_to_bytearray(cmd) message = DenaliMessage.build_message(channel_id=DenaliChannels.dialin_to_hd_ch_id, message_id=self.MSG_ID_HD_UF_PAUSE_RESUME_REQUEST, payload=payload) if cmd == self.UF_CMD_PAUSE: str_cmd = "pause" else: str_cmd = "resume" print("Sending UF " + str_cmd + " command.") self.can_interface.send(message, 0) return 0 def cmd_ui_uf_settings_change_request(self, vol=0.0): """ Constructs and sends a ui UF change settings command message \param vol (float): new ultrafiltration volume setting (in L) \returns none """ # reset response to this command so we can tell when response is received self.UFChangeResponse = None # build command message volume = float_to_bytearray(vol * self.LITER_TO_ML_CONVERSION_FACTOR) message = DenaliMessage.build_message(channel_id=DenaliChannels.dialin_to_hd_ch_id, message_id=self.MSG_ID_UF_SETTINGS_CHANGE_REQUEST_BY_USER, payload=volume) print("Sending UF settings change request.") self.can_interface.send(message, 0) return 0 def cmd_ui_uf_settings_change_confirm(self, vol=0.0, adj=UF_CMD_CHANGE_TIME_TO_ADJUST): """ Constructs and sends a ui UF change settings command message \param vol (float): new ultrafiltration volume setting (in L) \param adj (int): 0 for adjust time, 1 for adjust rate \returns none """ # reset response to this command so we can tell when response is received self.UFChangeResponse = None # build command message volume = float_to_bytearray(vol * self.LITER_TO_ML_CONVERSION_FACTOR) adjust = integer_to_bytearray(adj) payload = volume + adjust message = DenaliMessage.build_message(channel_id=DenaliChannels.dialin_to_hd_ch_id, message_id=self.MSG_ID_UF_SETTINGS_CHANGE_REQUEST_BY_USER, payload=payload) print("Sending UF settings change request.") self.can_interface.send(message, 0) return 0 def cmd_ui_uf_change_settings_confirmed_by_user(self, response=RESPONSE_REJECTED, vol=0.0, tm=0, rate=0.0): """ Constructs and sends a ui UF change settings confirmed by user message \param response (int): 0 for rejected, 1 for confirmed \param vol (float): volume (in L) that was confirmed \param tm (int): treatment time (in min) that was confirmed \param rate (float): ultrafiltration rate (in mL/min) that was confirmed \returns none """ resp = integer_to_bytearray(response) volume = float_to_bytearray(vol * self.LITER_TO_ML_CONVERSION_FACTOR) mins = integer_to_bytearray(tm) ufrate = float_to_bytearray(rate) payload = resp + volume + mins + ufrate message = DenaliMessage.build_message(channel_id=DenaliChannels.dialin_to_hd_ch_id, message_id=self.MSG_ID_UF_SETTINGS_CHANGE_CONFIRMED_BY_USER, payload=payload) print("Sending UF settings change confirmation.") self.can_interface.send(message, 0) return 0 def cmd_ui_treatment_duration_setting_change_request(self, timeMin=0): """ Constructs and sends a ui UF change settings confirmed by user message \param timeMin (int): treatment time (in min). \returns none """ payload = integer_to_bytearray(timeMin) message = DenaliMessage.build_message(channel_id=DenaliChannels.dialin_to_hd_ch_id, message_id=self.MSG_ID_TREATMENT_DURATION_SETTING_CHANGE_REQUEST, payload=payload) print("Sending treatment duration setting change request.") # Send message self.can_interface.send(message, 0) return 0 def cmd_ui_blood_and_dialysate_flow_settings_change_request(self, bloodFlow, dialFlow): """ Constructs and sends a ui blood & dialysate flow settings change request by user message \param bloodFlow (int): blood flow rate set point (in mL/min). \param dialFlow (int): dialysate flow rate set point (in mL/min). \returns none """ bld = integer_to_bytearray(bloodFlow) dial = integer_to_bytearray(dialFlow) payload = bld + dial message = DenaliMessage.build_message(channel_id=DenaliChannels.dialin_to_hd_ch_id, message_id=self.MSG_ID_BLOOD_DIALYSATE_FLOW_SETTING_CHANGE_REQUEST_BY_USER, payload=payload) print("Sending blood & dialysate flow rate settings change request.") self.can_interface.send(message, 0) return 0