Index: dialin/common/hd_defs.py =================================================================== diff -u -r6e5fe18fa126baffc94a09c050312ac00d98c288 -r0bd48437395256cf0ab9dc291705a29f776569b6 --- dialin/common/hd_defs.py (.../hd_defs.py) (revision 6e5fe18fa126baffc94a09c050312ac00d98c288) +++ dialin/common/hd_defs.py (.../hd_defs.py) (revision 0bd48437395256cf0ab9dc291705a29f776569b6) @@ -42,6 +42,31 @@ @unique +class HDInitStates(DialinEnum): + POST_STATE_START = 0 + POST_STATE_FW_INTEGRITY = 1 + POST_STATE_AC = 2 + POST_STATE_WATCHDOG = 3 + POST_STATE_SAFETY_SHUTDOWN = 4 + POST_STATE_RTC = 5 + POST_STATE_NVDATAMGMT = 6 + POST_STATE_BLOOD_FLOW = 7 + POST_STATE_DIALYSATE_FLOW = 8 + POST_STATE_VALVES = 9 + POST_STATE_SYRINGE_PUMP = 10 + POST_STATE_ALARM_AUDIO = 11 + POST_STATE_ALARM_LAMP = 12 + POST_STATE_ACCELEROMETER = 13 + POST_STATE_STUCK_BUTTON = 14 + POST_STATE_UI_POST = 15 + POST_STATE_FW_COMPATIBILITY = 16 + POST_STATE_FPGA = 17 + POST_STATE_COMPLETED = 18 + POST_STATE_FAILED = 19 + NUM_OF_POST_STATES = 20 + + +@unique class PreTreatmentSubModes(DialinEnum): HD_PRE_TREATMENT_START_STATE = 0 HD_PRE_TREATMENT_WATER_SAMPLE_STATE = 1 @@ -267,6 +292,14 @@ @unique +class TreatmentParametersStates(DialinEnum): + HD_TREATMENT_PARAMS_MODE_STATE_START = 0 + HD_TREATMENT_PARAMS_MODE_STATE_WAIT_4_UI_2_SEND = 1 + HD_TREATMENT_PARAMS_MODE_STATE_WAIT_4_UI_2_CONFIRM = 2 + NUM_OF_HD_TREATMENT_PARAMS_MODE_STATES = 3 + + +@unique class StandbyStates(DialinEnum): STANDBY_START_STATE = 0 # Start standby STANDBY_WAIT_FOR_TREATMENT_STATE = 1 # Wait for treatment @@ -332,3 +365,27 @@ TREATMENT_END_WAIT_FOR_RINSEBACK_STATE = 0 TREATMENT_END_PAUSED_STATE = 1 NUM_OF_TREATMENT_END_STATES = 2 + + +@unique +class HDFaultStates(DialinEnum): + HD_FAULT_STATE_START = 0 + NUM_OF_HD_FAULT_STATES = 1 + + +@unique +class HDEventList(DialinEnum): + HD_EVENT_STARTUP = 0 + HD_EVENT_OP_MODE_CHANGE = 1 + HD_EVENT_SUB_MODE_CHANGE = 2 + NUM_OF_HD_EVENT_IDS = 3 + + +@unique +class HDEventDataType(DialinEnum): + EVENT_DATA_TYPE_NONE = 0 + EVENT_DATA_TYPE_U32 = 1 + EVENT_DATA_TYPE_S32 = 2 + EVENT_DATA_TYPE_F32 = 3 + EVENT_DATA_TYPE_BOOL = 4 + NUM_OF_EVENT_DATA_TYPES = 5 \ No newline at end of file Index: dialin/dg/events.py =================================================================== diff -u -rbe5fbabd50dc31e5e5a5e4b99893fb8b92d016bd -r0bd48437395256cf0ab9dc291705a29f776569b6 --- dialin/dg/events.py (.../events.py) (revision be5fbabd50dc31e5e5a5e4b99893fb8b92d016bd) +++ dialin/dg/events.py (.../events.py) (revision 0bd48437395256cf0ab9dc291705a29f776569b6) @@ -155,8 +155,6 @@ # Convert the event ID to enum event_state_name = DGEventList(event_id).name - #print(event_state_name, event_data_1, event_data_2) - # 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: Index: dialin/hd/hd_events.py =================================================================== diff -u --- dialin/hd/hd_events.py (revision 0) +++ dialin/hd/hd_events.py (revision 0bd48437395256cf0ab9dc291705a29f776569b6) @@ -0,0 +1,209 @@ + +import struct +from logging import Logger +from ..common import * +from ..common.msg_defs import MsgIds, MsgFieldPositions +from ..protocols.CAN import DenaliChannels +from ..utils.base import AbstractSubSystem, publish +from datetime import datetime + + +class HDEvents(AbstractSubSystem): + """ + Hemodialysis (HD) Dialin API sub-class for events related commands. + """ + + def __init__(self, can_interface, logger: Logger): + """ + + @param can_interface: Denali CAN Messenger object + """ + + super().__init__() + self.can_interface = can_interface + self.logger = logger + + if self.can_interface is not None: + channel_id = DenaliChannels.hd_to_ui_ch_id + msg_id = MsgIds.MSG_ID_HD_EVENT.value + self.can_interface.register_receiving_publication_function(channel_id, msg_id, self._handler_events_sync) + + # Define the dictionaries + self._hd_event_dictionary = dict() + self._hd_event_data_type = dict() + + # Dictionary of the the mode as key and the sub mode states enum class as the value + self._hd_op_mode_2_sub_mode = {HDOpModes.MODE_FAUL.name: HDFaultStates, + HDOpModes.MODE_INIT.name: HDInitStates, + HDOpModes.MODE_STAN.name: StandbyStates, + HDOpModes.MODE_TPAR.name: TreatmentParametersStates, + HDOpModes.MODE_PRET.name: PreTreatmentSubModes, + HDOpModes.MODE_TREA.name: TreatmentStates, + HDOpModes.MODE_POST.name: PostTreatmentStates} + + # Loop through the list of the HD events enums and initial the event dictionary. Each event is a key in the + # dictionary and the value is a list. + for event in HDEventList: + self._hd_event_dictionary[HDEventList(event).name] = [] + + # Loop through the list of the event data type enum and update the dictionary + for data_type in HDEventDataType: + event_data_type = HDEventDataType(data_type).name + struct_unpack_type = None + + # If U32 is in the data type enum (i.e. EVENT_DATA_TYPE_U32), then the key is the enum and the value is + # the corresponding format in the python struct + if 'U32' in event_data_type or 'BOOL' in event_data_type: + struct_unpack_type = 'I' + elif 'S32' in event_data_type: + struct_unpack_type = 'i' + elif 'F32' in event_data_type: + struct_unpack_type = 'f' + + self._hd_event_data_type[event_data_type] = struct_unpack_type + + def get_hd_nth_event(self, event_id, event_number=0): + """ + Returns the nth requested HD event + + @param event_id the ID of the HD event types (i.e. HD_EVENT_STARTUP) + @param event_number the event number that is requested. The default is 0 meaning the last occurred event + + @returns the requested HD event number + """ + list_length = len(self._hd_event_dictionary[HDEventList(event_id).name]) + + if list_length == 0: + event = [] + elif event_number > list_length: + event = self._hd_event_dictionary[HDEventList(event_id).name][list_length - 1] + else: + event = self._hd_event_dictionary[HDEventList(event_id).name][list_length - event_number - 1] + + return event + + def get_hd_events(self, event_id, number_of_events=1): + """ + Returns the requested number of a certain HD event ID + + @param event_id the ID of the HD event types (i.e. HD_EVENT_STARTUP) + @param number_of_events the last number of messages of a certain event type + + @returns a list of the requested HD event type + """ + list_of_events = [] + + # If there are not enough event lists send all the events that are available + if len(self._hd_event_dictionary[HDEventList(event_id).name]) <= number_of_events: + list_of_events = self._hd_event_dictionary[HDEventList(event_id).name] + else: + # Get the all the events + complete_list = self._hd_event_dictionary[HDEventList(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, len(complete_list) - number_of_events - 1, -1): + list_of_events.append(complete_list[i]) + + if number_of_events == 0: + list_of_events = self._hd_event_dictionary[HDEventList(event_id).name] + + return list_of_events + + @publish(['_hd_event_dictionary']) + def _handler_events_sync(self, message): + """ + Handles published events message + + @param message: published HD events data message + @returns none + """ + event_id = struct.unpack('i', bytearray( + message['message'][MsgFieldPositions.START_POS_FIELD_1:MsgFieldPositions.END_POS_FIELD_1]))[0] + + # Get the data type + event_data_type_1 = struct.unpack('i', bytearray( + message['message'][MsgFieldPositions.START_POS_FIELD_2:MsgFieldPositions.END_POS_FIELD_2]))[0] + # Get the corresponding structure format + struct_data_type = self._hd_event_data_type[HDEventDataType(event_data_type_1).name] + # Get the data value by unpacking the data type + event_data_1 = struct.unpack(struct_data_type, bytearray( + message['message'][MsgFieldPositions.START_POS_FIELD_3:MsgFieldPositions.END_POS_FIELD_3]))[0] + + event_data_type_2 = struct.unpack('i', bytearray( + message['message'][MsgFieldPositions.START_POS_FIELD_4:MsgFieldPositions.END_POS_FIELD_4]))[0] + struct_data_type = self._hd_event_data_type[HDEventDataType(event_data_type_2).name] + event_data_2 = struct.unpack(struct_data_type, bytearray( + message['message'][MsgFieldPositions.START_POS_FIELD_5:MsgFieldPositions.END_POS_FIELD_5]))[0] + + # Convert the event ID to enum + event_state_name = HDEventList(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 == HDEventList.HD_EVENT_OP_MODE_CHANGE.name: + event_data_1 = HDOpModes(event_data_1).name + event_data_2 = HDOpModes(event_data_2).name + # Check if the event state name is sub mode change. + elif event_state_name == HDEventList.HD_EVENT_SUB_MODE_CHANGE.name: + # Get the length of the list of the sub mode list + op_list_len = len(self._hd_event_dictionary[HDEventList.HD_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._hd_event_dictionary[HDEventList.HD_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._hd_event_dictionary[HDEventList.HD_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._hd_event_dictionary[HDEventList.HD_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._hd_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._hd_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._hd_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 = (datetime.now().strftime('%Y-%m-%d %H:%M:%S.%f'), event_state_name, event_data_1, event_data_2) + + # Update event dictionary + self._hd_event_dictionary[event_state_name].append(event_tuple) Index: dialin/hd/hemodialysis_device.py =================================================================== diff -u -r6e5fe18fa126baffc94a09c050312ac00d98c288 -r0bd48437395256cf0ab9dc291705a29f776569b6 --- dialin/hd/hemodialysis_device.py (.../hemodialysis_device.py) (revision 6e5fe18fa126baffc94a09c050312ac00d98c288) +++ dialin/hd/hemodialysis_device.py (.../hemodialysis_device.py) (revision 0bd48437395256cf0ab9dc291705a29f776569b6) @@ -42,6 +42,7 @@ from .voltages import HDVoltages from .watchdog import HDWatchdog from ..common.hd_defs import HDOpModes +from .hd_events import HDEvents from ..common.msg_defs import MsgIds, MsgFieldPositions from ..protocols.CAN import DenaliMessage, DenaliCanMessenger, DenaliChannels from ..utils.base import AbstractSubSystem, publish, LogManager @@ -126,6 +127,7 @@ self.temperatures = HDTemperatures(self.can_interface, self.logger) self.fans = HDFans(self.can_interface, self.logger) self.watchdog = HDWatchdog(self.can_interface, self.logger) + self.hd_events = HDEvents(self.can_interface, self.logger) def get_operation_mode(self): """ Index: tests/dg_heat_and_chemical_disinfect_test.py =================================================================== diff -u -rbe5fbabd50dc31e5e5a5e4b99893fb8b92d016bd -r0bd48437395256cf0ab9dc291705a29f776569b6 --- tests/dg_heat_and_chemical_disinfect_test.py (.../dg_heat_and_chemical_disinfect_test.py) (revision be5fbabd50dc31e5e5a5e4b99893fb8b92d016bd) +++ tests/dg_heat_and_chemical_disinfect_test.py (.../dg_heat_and_chemical_disinfect_test.py) (revision 0bd48437395256cf0ab9dc291705a29f776569b6) @@ -28,6 +28,7 @@ from dialin.hd.temperatures import HDTemperaturesNames from dialin.common.hd_defs import HDOpModes, HDOpSubModes from dialin.common.dg_defs import DGEventList +from dialin.common.hd_defs import HDEventList from time import sleep from datetime import datetime import sys @@ -70,8 +71,10 @@ def get_hd_run_info(): - info = ('HD_op_mode, {}, HD_sub_mode, {}, '.format(HDOpModes(hd.hd_operation_mode).name, - hd.hd_operation_sub_mode)) + info = ('HD_op_mode, {}, HD_sub_mode, {}, Op, {}, Sub, {}, '.format(HDOpModes(hd.hd_operation_mode).name, + hd.hd_operation_sub_mode, + hd.hd_events.get_hd_events(HDEventList(1).value), + hd.hd_events.get_hd_events(HDEventList(2).value))) return info @@ -426,6 +429,8 @@ def collect_treatment_data(): f = open("/home/fw/projects/dialin/tests/treatment_run.log", "w") + dg.cmd_dg_software_reset_request() + try: while True: hd_run = get_hd_run_info() @@ -449,6 +454,22 @@ f.close() +def collect_hd_treatment(): + f = open("/home/fw/projects/dialin/tests/treatment_run_hd.log", "w") + + hd.cmd_hd_software_reset_request() + + try: + while True: + + sleep(1) + except KeyboardInterrupt: + events = hd.hd_events.get_hd_events(2, 0) + for event in events: + print(event) + f.close() + + def run_heat_disinfect(): complete_counter = 1 f = open("/home/fw/projects/dialin/tests/Heat_disinfect.log", "w") @@ -564,30 +585,9 @@ # collect_treatment_data() + collect_hd_treatment() - print(dg.rtc.get_rtc_epoch()) - sleep(0.1) - #dg.rtc.cmd_stop_rtc() - dg.rtc.cmd_set_rtc_time_and_date(45, 27, 17, 4, 11, 2021) - """ - sleep(6) - - print(dg.rtc.get_rtc_epoch()) - - sleep(1) - - dg.rtc.cmd_set_rtc_time_and_date(22, 18, 17, 4, 11, 2021) - - while True: - print(dg.rtc.get_rtc_epoch()) - sleep(1) - """ - - - - -