Index: dialin/common/msg_defs.py =================================================================== diff -u -r89df6a25e4845ad234c1ffc71fb484b73d125b4c -r835ce068c1989cd32821cd39737ac081fb856420 --- dialin/common/msg_defs.py (.../msg_defs.py) (revision 89df6a25e4845ad234c1ffc71fb484b73d125b4c) +++ dialin/common/msg_defs.py (.../msg_defs.py) (revision 835ce068c1989cd32821cd39737ac081fb856420) @@ -1,6 +1,6 @@ ########################################################################### # -# Copyright (c) 2019-2021 Diality Inc. - All Rights Reserved. +# Copyright (c) 2020-2022 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. @@ -23,8 +23,8 @@ @unique class MsgIdsDialin(DialinEnum): - MSG_DIALIN_ID_HD_SERIAL_NUMBER_RESPONSE = 0X87 - MSG_DIALIN_ID_DG_SERIAL_NUMBER_RESPONSE = 0X88 + MSG_DIALIN_ID_HD_SERIAL_NUMBER_RESPONSE = 0x87 + MSG_DIALIN_ID_DG_SERIAL_NUMBER_RESPONSE = 0x88 MSG_DIALIN_ID_UI_SYSTEM_USAGE_REQUEST = 0x89 MSG_DIALIN_ID_HD_SYSTEM_USAGE_RESPONSE = 0x8A MSG_DIALIN_ID_DG_SYSTEM_USAGE_RESPONSE = 0x8C @@ -48,7 +48,7 @@ REQUEST_REJECT_REASON_INVALID_TREATMENT_STATE = 4 REQUEST_REJECT_REASON_TREATMENT_TOO_CLOSE_TO_FINISHED = 5 REQUEST_REJECT_REASON_TREATMENT_TIME_OUT_OF_RANGE = 6 - EQUEST_REJECT_REASON_TREATMENT_TIME_LESS_THAN_CURRENT = 7 + REQUEST_REJECT_REASON_TREATMENT_TIME_LESS_THAN_CURRENT = 7 REQUEST_REJECT_REASON_BLOOD_FLOW_OUT_OF_RANGE = 8 REQUEST_REJECT_REASON_DIAL_FLOW_OUT_OF_RANGE = 9 REQUEST_REJECT_REASON_DIAL_VOLUME_OUT_OF_RANGE = 10 Index: dialin/dg/events.py =================================================================== diff -u -r5a413e437f8a341c9e9b77342df6e634c1563add -r835ce068c1989cd32821cd39737ac081fb856420 --- dialin/dg/events.py (.../events.py) (revision 5a413e437f8a341c9e9b77342df6e634c1563add) +++ dialin/dg/events.py (.../events.py) (revision 835ce068c1989cd32821cd39737ac081fb856420) @@ -1,8 +1,22 @@ +########################################################################### +# +# Copyright (c) 2021-2022 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 events.py +# +# @author (last) Dara Navaei +# @date (last) 16-Nov-2021 +# @author (original) Dara Navaei +# @date (original) 12-Oct-2021 +# +############################################################################ import struct from logging import Logger - -from ..common import DGEventList, DGEventDataType +from ..common import * from ..common.msg_defs import MsgIds, MsgFieldPositions from ..protocols.CAN import DenaliChannels from ..utils.base import AbstractSubSystem, publish @@ -33,6 +47,17 @@ self._dg_event_dictionary = dict() self._dg_event_data_type = dict() + # Dictionary of the the mode as key and the sub mode states enum class as the value + self._dg_op_mode_2_sub_mode = {DGOpModes.DG_MODE_FAUL.name: DGFaultStates, + DGOpModes.DG_MODE_INIT.name: DGInitStates, + DGOpModes.DG_MODE_STAN.name: DGStandByModeStates, + DGOpModes.DG_MODE_GENE.name: DGGenIdleModeStates, + DGOpModes.DG_MODE_FILL.name: DGFillModeStates, + DGOpModes.DG_MODE_DRAI.name: DGDrainModeStates, + DGOpModes.DG_MODE_FLUS.name: DGFlushStates, + DGOpModes.DG_MODE_HEAT.name: DGHeatDisinfectStates, + DGOpModes.DG_MODE_CHEM.name: DGChemicalDisinfectStates} + # Loop through the list of the DG events enums and initial the event dictionary. Each event is a key in the # dictionary and the value is a list. for event in DGEventList: @@ -91,20 +116,22 @@ else: # Get the all the events complete_list = self._dg_event_dictionary[DGEventList(event_id).name] - # Since the last are located at the end of the list, iterate backwards for the defined # event messages - for i in range(len(complete_list) - 1, number_of_events + 1, -1): + for i in range(len(complete_list) - 1, len(complete_list) - number_of_events - 1, -1): list_of_events.append(complete_list[i]) + if number_of_events == 0: + list_of_events = self._dg_event_dictionary[DGEventList(event_id).name] + return list_of_events @publish(['_dg_event_dictionary']) def _handler_events_sync(self, message): """ Handles published events message - @param message: published DG events data message + @param message: published DG events data message @returns none """ event_id = struct.unpack('i', bytearray( @@ -129,8 +156,72 @@ # Convert the event ID to enum event_state_name = DGEventList(event_id).name + # Check if the event state name is operation mode change. If it is, get the name of the operation modes + # from the op modes enum class + if event_state_name == DGEventList.DG_EVENT_OP_MODE_CHANGE.name: + event_data_1 = DGOpModes(event_data_1).name + event_data_2 = DGOpModes(event_data_2).name + # Check if the event state name is sub mode change. + elif event_state_name == DGEventList.DG_EVENT_SUB_MODE_CHANGE.name: + # Get the length of the list of the sub mode list + op_list_len = len(self._dg_event_dictionary[DGEventList.DG_EVENT_OP_MODE_CHANGE.name]) + # Get the last tuple of the sub mode + # It is a list of tuples that each tuple is (timestamp, event type, prev op mode, current op mode) + last_op_tuple = self._dg_event_dictionary[DGEventList.DG_EVENT_OP_MODE_CHANGE.name][op_list_len - 1] + + # Get the current and previous operation modes of the last tuple in the list of the sub modes + # i.e. (timestamp, event type, prev, current) + current_op_mode = last_op_tuple[len(last_op_tuple) - 1] + current_op_mode_timestamp = datetime.strptime(last_op_tuple[0], '%Y-%m-%d %H:%M:%S.%f') + + sub_mode_list_len = len(self._dg_event_dictionary[DGEventList.DG_EVENT_SUB_MODE_CHANGE.name]) + + if sub_mode_list_len != 0: + # Get the tuple prior to the last tuple and get its previous and current operation modes + current_sub_tuple = self._dg_event_dictionary[DGEventList.DG_EVENT_SUB_MODE_CHANGE.name][ + sub_mode_list_len - 1] + + current_sub_mode_timestamp = datetime.strptime(current_sub_tuple[0], '%Y-%m-%d %H:%M:%S.%f') + else: + current_sub_mode_timestamp = 0 + + # Get the class of the states enums of the current operation mode that is running + current_sub_mode_enum_class = self._dg_op_mode_2_sub_mode[current_op_mode] + + # Check if the operation modes of the two tuples match + # i.e. last = (timestamp, event type, prev, current) and one before = (timestamp, event type, prev, current) + # If the prev and current match respectively, it means the current operation mode has not changed so the + # operation mode states can be converted from the current sub mode enum class + if current_sub_mode_timestamp != 0: + if current_op_mode_timestamp <= current_sub_mode_timestamp: + + event_data_1 = current_sub_mode_enum_class(event_data_1).name + event_data_2 = current_sub_mode_enum_class(event_data_2).name + + elif current_op_mode_timestamp > current_sub_mode_timestamp: + # If the previous and current of the last two tuples do not match, then an operation mode transition + # has occurred and the previous state is converted from the previous class and the current op mode + # is converted from current operation states enum class. + # i.e last = (timestamp, event type, 3, 8) and one before = (timestamp, event type, 8, 3) + # previous and current do not match so in the last type (timestamp, event type, 8, 3) the prev state + # should be from op mode 8 and the current state should be from op mode 3 + previous_op_mode = last_op_tuple[len(last_op_tuple) - 2] + previous_sub_mode_enum_class = self._dg_op_mode_2_sub_mode[previous_op_mode] + event_data_1 = previous_sub_mode_enum_class(event_data_1).name + event_data_2 = current_sub_mode_enum_class(event_data_2).name + else: + + if event_data_2 != 0: + event_data_1 = current_sub_mode_enum_class(event_data_1).name + event_data_2 = current_sub_mode_enum_class(event_data_2).name + else: + previous_sub_mode = current_sub_tuple[len(current_sub_tuple) - 2] + previous_sub_mode_enum_class = self._dg_op_mode_2_sub_mode[previous_sub_mode] + event_data_1 = previous_sub_mode_enum_class(event_data_1).name + event_data_2 = current_sub_mode_enum_class(event_data_2).name + # Get the current timestamp and create a tuple of the current events - event_tuple = (str(datetime.now()), event_state_name, event_data_1, event_data_2) + event_tuple = (datetime.now().strftime('%Y-%m-%d %H:%M:%S.%f'), event_state_name, event_data_1, event_data_2) # Update event dictionary self._dg_event_dictionary[event_state_name].append(event_tuple) Index: dialin/dg/ro_pump.py =================================================================== diff -u -r89df6a25e4845ad234c1ffc71fb484b73d125b4c -r835ce068c1989cd32821cd39737ac081fb856420 --- dialin/dg/ro_pump.py (.../ro_pump.py) (revision 89df6a25e4845ad234c1ffc71fb484b73d125b4c) +++ dialin/dg/ro_pump.py (.../ro_pump.py) (revision 835ce068c1989cd32821cd39737ac081fb856420) @@ -1,14 +1,14 @@ ########################################################################### # -# Copyright (c) 2019-2021 Diality Inc. - All Rights Reserved. +# Copyright (c) 2020-2022 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 ro_pump.py # -# @author (last) Quang Nguyen -# @date (last) 05-Aug-2021 +# @author (last) Dara Navaei +# @date (last) 23-Feb-2022 # @author (original) Sean # @date (original) 14-Apr-2020 # @@ -59,11 +59,11 @@ self.target_pressure_psi = 0.0 self.measured_flow_rate_lpm = 0.0 self.pwm_duty_cycle_pct = 0.0 - self.ro_pump_state = 0.0 + self.ro_pump_state = 0 self.target_flow_lpm = 0.0 + self.feedback_duty_cycle_pct = 0.0 + self.measured_raw_flow_rate_mlp = 0.0 - self.temporary_flow_value = 0.0 - def get_target_pressure(self): """ Gets the target pressure @@ -96,7 +96,16 @@ """ return self.ro_pump_state - @publish(["target_pressure_psi", "measured_flow_rate_lpm", "pwm_duty_cycle_pct", "ro_pump_state"]) + def get_ro_pump_measured_raw_flow_rate_mlp(self): + """ + Gets the RO pump measured raw flow rate + + @return: The RO pump measured raw flow rate in mL/min + """ + return self.measured_raw_flow_rate_mlp + + @publish(["target_pressure_psi", "measured_flow_rate_lpm", "pwm_duty_cycle_pct", "ro_pump_state", + "feedback_duty_cycle_pct", "measured_raw_flow_rate_mlp"]) def _handler_ro_pump_sync(self, message): """ Handles published ro pump data messages. RO pump data are captured @@ -106,26 +115,22 @@ @return: None """ - tgt_pres = struct.unpack('f', bytearray( + self.target_pressure_psi = struct.unpack('f', bytearray( message['message'][MsgFieldPositions.START_POS_FIELD_1:MsgFieldPositions.END_POS_FIELD_1]))[0] - flow = struct.unpack('f', bytearray( + self.measured_flow_rate_lpm = struct.unpack('f', bytearray( message['message'][MsgFieldPositions.START_POS_FIELD_2:MsgFieldPositions.END_POS_FIELD_2]))[0] - pwm = struct.unpack('f', bytearray( + self.pwm_duty_cycle_pct = struct.unpack('f', bytearray( message['message'][MsgFieldPositions.START_POS_FIELD_3:MsgFieldPositions.END_POS_FIELD_3]))[0] ro_state = struct.unpack('i', bytearray( message['message'][MsgFieldPositions.START_POS_FIELD_4:MsgFieldPositions.END_POS_FIELD_4]))[0] - tgt_flow = struct.unpack('f', bytearray( + self.target_flow_lpm = struct.unpack('f', bytearray( message['message'][MsgFieldPositions.START_POS_FIELD_5:MsgFieldPositions.END_POS_FIELD_5]))[0] - - temp_current_flow = struct.unpack('f', bytearray( + self.feedback_duty_cycle_pct = struct.unpack('f', bytearray( message['message'][MsgFieldPositions.START_POS_FIELD_6:MsgFieldPositions.END_POS_FIELD_6]))[0] - self.temporary_flow_value = temp_current_flow + self.measured_raw_flow_rate_mlp = struct.unpack('f', bytearray( + message['message'][MsgFieldPositions.START_POS_FIELD_7:MsgFieldPositions.END_POS_FIELD_7]))[0] - self.target_pressure_psi = tgt_pres - self.measured_flow_rate_lpm = flow - self.pwm_duty_cycle_pct = pwm - self.ro_pump_state = ROPumpStates(ro_state).name if ROPumpStates.has_value(ro_state) else 'State Unknown' - self.target_flow_lpm = tgt_flow + self.ro_pump_state = ROPumpStates(ro_state).name def cmd_ro_pump_duty_cycle_pct(self, duty: float) -> int: """ Index: dialin/hd/ui_proxy.py =================================================================== diff -u -r34b1f65130d6d28816abc05ec95d01ef02067317 -r835ce068c1989cd32821cd39737ac081fb856420 --- dialin/hd/ui_proxy.py (.../ui_proxy.py) (revision 34b1f65130d6d28816abc05ec95d01ef02067317) +++ dialin/hd/ui_proxy.py (.../ui_proxy.py) (revision 835ce068c1989cd32821cd39737ac081fb856420) @@ -1,14 +1,14 @@ ########################################################################### # -# Copyright (c) 2019-2021 Diality Inc. - All Rights Reserved. +# Copyright (c) 2020-2022 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_proxy.py # -# @author (last) Quang Nguyen -# @date (last) 11-Aug-2021 +# @author (last) Dara Navaei +# @date (last) 15-Oct-2021 # @author (original) Sean # @date (original) 15-Apr-2020 # Index: dialin/protocols/CAN.py =================================================================== diff -u -r89df6a25e4845ad234c1ffc71fb484b73d125b4c -r835ce068c1989cd32821cd39737ac081fb856420 --- dialin/protocols/CAN.py (.../CAN.py) (revision 89df6a25e4845ad234c1ffc71fb484b73d125b4c) +++ dialin/protocols/CAN.py (.../CAN.py) (revision 835ce068c1989cd32821cd39737ac081fb856420) @@ -1,6 +1,6 @@ ########################################################################### # -# Copyright (c) 2019-2021 Diality Inc. - All Rights Reserved. +# Copyright (c) 2020-2022 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. Index: dialin/ui/hd_simulator.py =================================================================== diff -u -r033f788565cc4c77c876008df8a51d1e67482eaa -r835ce068c1989cd32821cd39737ac081fb856420 --- dialin/ui/hd_simulator.py (.../hd_simulator.py) (revision 033f788565cc4c77c876008df8a51d1e67482eaa) +++ dialin/ui/hd_simulator.py (.../hd_simulator.py) (revision 835ce068c1989cd32821cd39737ac081fb856420) @@ -1,20 +1,21 @@ ########################################################################### # -# Copyright (c) 2019-2021 Diality Inc. - All Rights Reserved. +# Copyright (c) 2020-2022 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) Quang Nguyen -# @date (last) 11-Aug-2021 +# @author (last) Micahel Garthwaite +# @date (last) 09-Mar-2022 # @author (original) Peter Lucia # @date (original) 06-Aug-2020 # ############################################################################ import enum from time import sleep +from typing import Callable from . import messageBuilder from ..common import * @@ -42,7 +43,7 @@ def __init__(self, can_interface: str = "can0", log_level: bool = None, console_out: bool = False, - passive_mode: bool = False, + passive_mode: bool = True, auto_response: bool = False): """ The HDSimulator constructor @@ -54,8 +55,7 @@ super().__init__() HDSimulator.instance_count = HDSimulator.instance_count + 1 - self.checked_in = False - self.checked_in_last = 0 + 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 @@ -68,12 +68,8 @@ if self.can_interface is not None: channel_id = DenaliChannels.ui_to_hd_ch_id - if auto_response: self.can_interface.register_receiving_publication_function(channel_id, - MsgIds.MSG_ID_UI_CHECK_IN.value, - self._handler_ui_first_check_in) - self.can_interface.register_receiving_publication_function(channel_id, MsgIds.MSG_ID_UI_INITIATE_TREATMENT_REQUEST.value, self._handler_ui_initiate_treatment) self.can_interface.register_receiving_publication_function(channel_id, @@ -109,6 +105,24 @@ # initialize variables that will be populated by UI version response self.ui_version = None + def add_publication(self, channel_id: DenaliChannels, message_id: MsgIds, function_ptr: Callable) -> None: + """ + Allows later addition of publication to the HDSimulator + @param channel_id: (DenaliChannels) the channel id of the message + @param message_id: (MsgIds) the message id + @param function_ptr: (Callable) the pointer to the message handler function + @return: None + """ + if self.auto_response: + if channel_id > 0 and message_id != MsgIds.MSG_ID_UNUSED and function_ptr is not None: + self.can_interface.register_receiving_publication_function(channel_id, + message_id, + function_ptr) + else: + self.logger.debug("rejected publication registration {0}, {1}, {2}".format(channel_id, + message_id, + function_ptr)) + def get_ui_version(self): """ Gets the ui version @@ -310,33 +324,6 @@ self.cmd_send_uf_treatment_response(1, 0, uf_volume) - def _handler_ui_first_check_in(self, message)->None: - """ - Handler function to first check in to start the post - @param message: the check-in message - @return: None - """ - now = time.time() - # if the application is not checking-in (the simulatoe ) within 2 sec it means it has been stopped, - # so do the check-in again. - print("check-in: ", now, now - self.checked_in_last) - if now - self.checked_in_last > 5: - self.checked_in = False - - self.checked_in_last = now - - if self.checked_in: - return - - print("_handler_ui_first_check_in") - self.cmd_send_power_on_self_test_version_request() - for i in range(20): - self.cmd_send_hd_post(i, True, False) - sleep(0.1) - self.cmd_send_hd_post(0, True, True) - self.cmd_send_hd_operation_mode(4, 0) - self.checked_in = True - def _handler_ui_initiate_treatment(self, message): """ Handler function to start a treatment @@ -646,13 +633,13 @@ 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, signal_strength: float) -> None: + mc_current: float, pwm: float, rotor_count: 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:(F32) | + | 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::mSigStrenght | + |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 @@ -661,7 +648,7 @@ @param mc_speed: (float) MC Speed @param mc_current: (float) MC Current @param pwm: (float) PWM - @param signal_strength: (float) Signal strength in percent + @param rotor_count: (int) Rotor Count @return: None """ @@ -672,7 +659,7 @@ payload += float_to_bytearray(mc_speed) payload += float_to_bytearray(mc_current) payload += float_to_bytearray(pwm) - payload += float_to_bytearray(signal_strength) + payload += integer_to_bytearray(rotor_count) message = DenaliMessage.build_message(channel_id=DenaliChannels.hd_sync_broadcast_ch_id, message_id=MsgIds.MSG_ID_BLOOD_FLOW_DATA.value, @@ -682,13 +669,13 @@ 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, signal_strength: float) -> None: + mc_current: float, pwm: float) -> 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) | #8:(F32) | - |:----:|:------:|:---:|:------:|:---:|:---:|:---:|:-----------: |:--: |:--: |:--: |:--: |:--: |:--: |:--: |:--: | - |0x0800| 0x040 | 7 | 1 Hz | N | HD | All | Dialysate Flow Data | mFlowSetPoint | mMeasuredFlow | mRotorSpeed | mMotorSpeed | mMotorCtlSpeed | mMotorCtlCurrent | mPWMDutyCycle | \ref Data::mSigStrenght | + | 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 @@ -697,7 +684,6 @@ @param mc_speed: (float) MC Speed @param mc_current: (float) MC Current @param pwm: (float) PWM - @param signal_strength: (float) Signal strength in percent @return: None """ @@ -708,7 +694,6 @@ payload += float_to_bytearray(mc_speed) payload += float_to_bytearray(mc_current) payload += float_to_bytearray(pwm) - payload += float_to_bytearray(signal_strength) message = DenaliMessage.build_message(channel_id=DenaliChannels.hd_sync_broadcast_ch_id, message_id=MsgIds.MSG_ID_DIALYSATE_FLOW_DATA.value, @@ -1014,7 +999,7 @@ 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) -> None: + 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 | @@ -1029,19 +1014,23 @@ |:--: |:--: |:--: || | \ref Data::mHeparinState | \ref Data::mRinsebackState | \ref Data::mRecirculateState || |||| - | #7:(U32) | #8:(U32) | #9:(U32) || - |:--: |:--: |:--: || - | \ref Data::vBloodPrimeState | \ref Data::mTreatmentEndState | \ref Data::vTreammentStopState || + | #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) Heparin 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 """ @@ -1054,6 +1043,7 @@ 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.value, @@ -1119,12 +1109,12 @@ self.can_interface.send(message, 0) - def cmd_set_saline_bolus_response(self, accepted: int, reason: int, target: int, state: int) -> None: + 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) | #3:(U32) | - |:---:|:------:|:-----:|:----:|:---:|:---:|:----:|:---------------------:|:--------------------:|:-------------------:|:-------------------:|:-------------------:| - | 20 | 0x020 | 6 | Rsp | Y | HD | UI | Saline Bolus Response | \ref Data::mAccepted | \ref Data::mReason | \ref Data::mTarget | \ref Data::mState | + | 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 @@ -1136,7 +1126,6 @@ payload = integer_to_bytearray(accepted) payload += integer_to_bytearray(reason) payload += integer_to_bytearray(target) - payload += integer_to_bytearray(state) message = DenaliMessage.build_message(channel_id=DenaliChannels.hd_to_ui_ch_id, message_id=MsgIds.MSG_ID_USER_SALINE_BOLUS_RESPONSE.value, @@ -1192,21 +1181,19 @@ self.can_interface.send(message, 0) - def cmd_set_heparin_pause_resume_response(self, accepted: int, reason: int, state: int) -> None: + 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) | #3:(U32) | - |:----:|:------:|:-----:|:----:|:---:|:---:|:----:|:----------------:|:--------------------:|:-------------------:|:-------------------:| - |0x4C00| 0x020 | 6 | Rsp | Y | HD | UI | Heparin Response | \ref Data::mAccepted | \ref Data::mReason | \ref Data::mState | + | 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 - @param state: (int) Heparin current 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_HD_HEPARIN_PAUSE_RESUME_RESPONSE.value, @@ -1266,28 +1253,29 @@ self.can_interface.send(message, 0) - def cmd_send_treatment_rinseback_data(self, vTarget, vCurrent, vRate, vTimeoutTotal, vTimeoutCountDown, vSafetyVolume): + 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) | - |:----:|:------:|:---:|:------:|:---:|:---:|:---:|:-----------: |:--: |:--: |:--: |:--: |:--: | - |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 | + | 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 vTarget : (float) the target volume in mL - :param vCurrent : (float) the current volume in mL - :param vRate : (uint ) the current flow rate in mL/min - :param vTimeoutTotal : (uint ) Total Timeout - :param vTimeoutCountDown: (uint ) Current Timeout count down - :param vSafetyVolume : (float) Safety Volume - :return: None + @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(vTarget) - payload += float_to_bytearray(vCurrent) - payload += integer_to_bytearray(vRate) - payload += integer_to_bytearray(vTimeoutTotal) - payload += integer_to_bytearray(vTimeoutCountDown) - payload += float_to_bytearray(vSafetyVolume) + 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, @@ -1330,6 +1318,7 @@ payload = float_to_bytearray(target) payload += float_to_bytearray(current) + payload += float_to_bytearray(0) message = DenaliMessage.build_message(channel_id=DenaliChannels.hd_to_ui_ch_id, message_id=MsgIds.MSG_ID_HD_BLOOD_PRIME_PROGRESS.value, @@ -1473,17 +1462,18 @@ 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) -> None: + fpga_id: int, fpga_major: int, fpga_minor: int, fpga_lab: int, compatibility_rev: int) -> None: """ the hd version response message method - @param major: integer - Major version number - @param minor: integer - Minor version number - @param micro: integer - Micro version number - @param build: integer - Build version number - @param fpga_id: integer - FPGA id version number - @param fpga_major: integer - FPGA Major version number - @param fpga_minor: integer - FPGA Minor version number - @param fpga_lab: integer - FPGA Lab version number + @param major: (integer) - Major version number + @param minor: (integer) - Minor version number + @param micro: (integer) - Micro version number + @param build: (integer) - Build version number + @param fpga_id: (integer) - FPGA id version number + @param fpga_major: (integer) - FPGA Major version number + @param fpga_minor: (integer) - FPGA Minor version number + @param fpga_lab: (integer) - FPGA Lab version number + @param compatibility_rev: (integer) - Compatibility revision @return: None """ @@ -1495,6 +1485,7 @@ payload += byte_to_bytearray(fpga_major) payload += byte_to_bytearray(fpga_minor) payload += byte_to_bytearray(fpga_lab) + payload += 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, @@ -1685,22 +1676,6 @@ 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: (U32) accept or reject - :param reason: (U32) 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, bood_flow_rate: int, dialysate_flow_rate: int, @@ -1880,7 +1855,8 @@ 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: + 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 @@ -1917,7 +1893,7 @@ def cmd_send_hd_disinfect_chemical_confirm(self, accepted: bool, reason: int) -> None: """ - the HD response to the UI sending the user chimical disinfection steps confirm. + 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 @@ -1938,21 +1914,20 @@ @param message: The ui version response message @return: 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])) - micro = struct.unpack('B', bytearray( - message['message'][self.START_POS_MICRO:self.END_POS_MICRO])) - build = struct.unpack('H', bytearray( - message['message'][self.START_POS_BUILD:self.END_POS_BUILD])) - compatibility = struct.unpack('H', bytearray( - message['message'][self.START_POS_COMPAT:self.END_POS_COMPAT])) + 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) - if all([len(each) > 0 for each in [major, minor, micro, build]]): - self.ui_version = f"v{major[0]}.{minor[0]}.{micro[0]}-{build[0]}-{compatibility[0]}" - self.logger.debug(f"UI VERSION: {self.ui_version}") + 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 @@ -2080,3 +2055,74 @@ "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_to_bytearray(blood_leak_status) + payload += unsigned_to_bytearray(blood_leak_state) + payload += unsigned_to_bytearray(blood_leak_zero_status_counter) + payload += unsigned_to_bytearray(blood_leak_counter) + payload += unsigned_to_bytearray(blood_leak_zeroed_status) + payload += unsigned_to_bytearray(blood_leak_detect_set_point) + payload += unsigned_to_bytearray(blood_leak_detect_level) + payload += unsigned_to_bytearray(blood_leak_st_count) + payload += unsigned_to_bytearray(blood_leak_led_intensity) + payload += unsigned_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_to_bytearray(lower_level) + payload += unsigned_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_to_bytearray(venous_air_bubble_status) + payload += unsigned_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) Index: tests/unit_tests/test_ui_proxy.py =================================================================== diff -u -r89df6a25e4845ad234c1ffc71fb484b73d125b4c -r835ce068c1989cd32821cd39737ac081fb856420 --- tests/unit_tests/test_ui_proxy.py (.../test_ui_proxy.py) (revision 89df6a25e4845ad234c1ffc71fb484b73d125b4c) +++ tests/unit_tests/test_ui_proxy.py (.../test_ui_proxy.py) (revision 835ce068c1989cd32821cd39737ac081fb856420) @@ -1,6 +1,6 @@ ########################################################################### # -# Copyright (c) 2019-2021 Diality Inc. - All Rights Reserved. +# Copyright (c) 2020-2022 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.