Index: leahi_dialin/dd/modules/events.py =================================================================== diff -u -r20c821bd230fc7689a0275a2918981669ff5cc19 -re45b20cdc5d4c5dcff8cef530b173ca94cb2e422 --- leahi_dialin/dd/modules/events.py (.../events.py) (revision 20c821bd230fc7689a0275a2918981669ff5cc19) +++ leahi_dialin/dd/modules/events.py (.../events.py) (revision e45b20cdc5d4c5dcff8cef530b173ca94cb2e422) @@ -8,21 +8,25 @@ # @file events.py # # @author (last) Zoltan Miskolci -# @date (last) 07-Jan-2026 +# @date (last) 04-May-2026 # @author (original) Dara Navaei # @date (original) 12-Oct-2021 # ############################################################################ +# Module imports import struct from logging import Logger from datetime import datetime -from time import time +# Project imports from leahi_dialin.common import dd_enum_repository -from leahi_dialin.common.msg_defs import MsgIds, MsgFieldPositions -from leahi_dialin.protocols.CAN import DenaliChannels -from leahi_dialin.utils.base import AbstractSubSystem, publish +from leahi_dialin.common.generic_defs import DataTypes +from leahi_dialin.common.msg_defs import MsgFieldPositions +from leahi_dialin.common.msg_ids import MsgIds +from leahi_dialin.protocols.CAN import CanMessenger, CanChannels +from leahi_dialin.utils.abstract_classes import AbstractSubSystem +from leahi_dialin.utils.base import publish @@ -33,69 +37,49 @@ """ UNKNOWN_STATE = "UNKNOWN_PREVIOUS_STATE" - def __init__(self, can_interface, logger: Logger): + def __init__(self, can_interface: CanMessenger, logger: Logger): """ - @param can_interface: Denali CAN Messenger object + @param can_interface: Can Messenger object """ super().__init__() self.can_interface = can_interface self.logger = logger if self.can_interface is not None: - channel_id = DenaliChannels.dd_sync_broadcast_ch_id - self.msg_id_dd_event = MsgIds.MSG_ID_DD_EVENT.value - self.can_interface.register_receiving_publication_function(channel_id, self.msg_id_dd_event, self._handler_events_sync) + self.can_interface.register_receiving_publication_function(channel_id = CanChannels.dd_sync_broadcast_ch_id, + message_id = MsgIds.MSG_ID_DD_EVENT.value, + function = self._handler_events_sync) + + self.can_interface.register_receiving_publication_function(channel_id = CanChannels.dd_sync_broadcast_ch_id, + message_id = MsgIds.MSG_ID_DD_OP_MODE_DATA.value, + function = self._handler_dd_op_mode_sync) - channel_id = DenaliChannels.dd_sync_broadcast_ch_id - self.msg_id_dd_op_mode_data = MsgIds.MSG_ID_DD_OP_MODE_DATA.value - self.can_interface.register_receiving_publication_function(channel_id, self.msg_id_dd_op_mode_data, - self._handler_dd_op_mode_sync) + self.events_timestamp = 0.0 #: The timestamp of the last Event message + self.op_mode = 0 #: The new Operation Mode value + self.sub_mode = 0 #: The new Operation Sub-Mode value + self.event_op_mode_timestamp = 0.0 #: The timestamp of the last Operation Mode change message - self.dd_events_timestamp = 0.0 #: The timestamp of the last Event message - self.dd_event_op_mode = 0 #: The new Operation Mode value - self.dd_event_sub_mode = 0 #: The new Operation Sub-Mode value - self.dd_event_op_mode_timestamp = 0.0 #: The timestamp of the last Operation Mode change message - # Dictionary of the mode as key and the sub mode states enum class as the value - self._dd_op_mode_2_sub_mode = {dd_enum_repository.DDOpModes.MODE_FAUL.name: dd_enum_repository.DDFaultStates, - dd_enum_repository.DDOpModes.MODE_SERV.name: dd_enum_repository.DDServiceStates, - dd_enum_repository.DDOpModes.MODE_INIT.name: dd_enum_repository.DDInitStates, - dd_enum_repository.DDOpModes.MODE_STAN.name: dd_enum_repository.DDStandbyStates, - dd_enum_repository.DDOpModes.MODE_PREG.name: dd_enum_repository.DDPreGenDialysateStates, - dd_enum_repository.DDOpModes.MODE_GEND.name: dd_enum_repository.DDGenDialysateModeStates, - dd_enum_repository.DDOpModes.MODE_POSG.name: dd_enum_repository.DDPostGenDialysateStates, - dd_enum_repository.DDOpModes.MODE_HEAT.name: dd_enum_repository.DDHeatDisinfectStates, - dd_enum_repository.DDOpModes.MODE_HCOL.name: dd_enum_repository.DDHeaterCoolingStates, - dd_enum_repository.DDOpModes.MODE_ROPS.name: dd_enum_repository.DDROPermeateStates, - dd_enum_repository.DDOpModes.MODE_NLEG.name: dd_enum_repository.DDNotLegalStates} + self._op_mode_2_sub_mode = {dd_enum_repository.DDOpModes.MODE_FAUL.name: dd_enum_repository.DDFaultStates, + dd_enum_repository.DDOpModes.MODE_SERV.name: dd_enum_repository.DDServiceStates, + dd_enum_repository.DDOpModes.MODE_INIT.name: dd_enum_repository.DDInitStates, + dd_enum_repository.DDOpModes.MODE_STAN.name: dd_enum_repository.DDStandbyStates, + dd_enum_repository.DDOpModes.MODE_PREG.name: dd_enum_repository.DDPreGenDialysateStates, + dd_enum_repository.DDOpModes.MODE_GEND.name: dd_enum_repository.DDGenDialysateModeStates, + dd_enum_repository.DDOpModes.MODE_POSG.name: dd_enum_repository.DDPostGenDialysateStates, + dd_enum_repository.DDOpModes.MODE_HEAT.name: dd_enum_repository.DDHeatDisinfectStates, + dd_enum_repository.DDOpModes.MODE_HCOL.name: dd_enum_repository.DDHeaterCoolingStates, + dd_enum_repository.DDOpModes.MODE_ROPS.name: dd_enum_repository.DDROPermeateStates, + dd_enum_repository.DDOpModes.MODE_NLEG.name: dd_enum_repository.DDNotLegalStates} - # Define the dictionaries - self._dd_event_dictionary = dict() - self._dd_event_data_type = dict() - # Loop through the list of the DD events enums and initial the event dictionary. Each event is a key in the # dictionary and the value is a list. + self._event_dictionary = dict() for event in dd_enum_repository.DDEventList: - self._dd_event_dictionary[dd_enum_repository.DDEventList(event).name] = [] + self._event_dictionary[dd_enum_repository.DDEventList(event).name] = [] - # Loop through the list of the event data type enum and update the dictionary - for data_type in dd_enum_repository.DDEventDataTypes: - event_data_type = dd_enum_repository.DDEventDataTypes(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._dd_event_data_type[event_data_type] = struct_unpack_type - - def get_dd_nth_event(self, event_id, event_number=0): """ Returns the nth requested DD event @@ -105,14 +89,14 @@ @returns the requested DD event number """ - list_length = len(self._dd_event_dictionary[dd_enum_repository.DDEventList(event_id).name]) + list_length = len(self._event_dictionary[dd_enum_repository.DDEventList(event_id).name]) if list_length == 0: event = [] elif event_number > list_length: - event = self._dd_event_dictionary[dd_enum_repository.DDEventList(event_id).name][list_length - 1] + event = self._event_dictionary[dd_enum_repository.DDEventList(event_id).name][list_length - 1] else: - event = self._dd_event_dictionary[dd_enum_repository.DDEventList(event_id).name][list_length - event_number - 1] + event = self._event_dictionary[dd_enum_repository.DDEventList(event_id).name][list_length - event_number - 1] return event @@ -123,8 +107,8 @@ @returns none """ - for key in self._dd_event_dictionary: - self._dd_event_dictionary[key].clear() + for key in self._event_dictionary: + self._event_dictionary[key].clear() def get_dd_events(self, event_id, number_of_events=1): @@ -139,23 +123,23 @@ list_of_events = [] # If there are not enough event lists send all the events that are available - if len(self._dd_event_dictionary[dd_enum_repository.DDEventList(event_id).name]) <= number_of_events: - list_of_events = self._dd_event_dictionary[dd_enum_repository.DDEventList(event_id).name] + if len(self._event_dictionary[dd_enum_repository.DDEventList(event_id).name]) <= number_of_events: + list_of_events = self._event_dictionary[dd_enum_repository.DDEventList(event_id).name] else: # Get the all the events - complete_list = self._dd_event_dictionary[dd_enum_repository.DDEventList(event_id).name] + complete_list = self._event_dictionary[dd_enum_repository.DDEventList(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._dd_event_dictionary[dd_enum_repository.DDEventList(event_id).name] + list_of_events = self._event_dictionary[dd_enum_repository.DDEventList(event_id).name] return list_of_events - @publish(["msg_id_dd_event", "dd_events_timestamp", '_dd_event_dictionary']) + @publish(["msg_id_dd_event", "events_timestamp", '_event_dictionary']) def _handler_events_sync(self, message, timestamp=0.0): """ Handles published events message @@ -170,132 +154,145 @@ sub_state = 0 current_sub_tuple = [] - event_id = struct.unpack('i', bytearray( + event_id = struct.unpack(DataTypes.U32.unpack_attrib(), bytearray( message['message'][MsgFieldPositions.START_POS_FIELD_1:MsgFieldPositions.END_POS_FIELD_1]))[0] + # Convert the event ID to enum + event_enum = dd_enum_repository.DDEventList(event_id) + current_timestamp = datetime.now().astimezone().strftime('%Y-%m-%d %H:%M:%S.%f') - if event_id == dd_enum_repository.DDEventList.DD_EVENT_OPERATION_STATUS.value: - # Get the data type - event_data_type_1 = struct.unpack('i', bytearray( + if event_enum is dd_enum_repository.DDEventList.DD_EVENT_OPERATION_STATUS: + # Get the data type - irrelevant + event_data_type_1 = struct.unpack(DataTypes.U32.unpack_attrib(), bytearray( message['message'][MsgFieldPositions.START_POS_FIELD_2:MsgFieldPositions.END_POS_FIELD_2]))[0] - struct_data_type = self._dd_event_data_type[dd_enum_repository.DDEventDataTypes(event_data_type_1).name] - op_mode = struct.unpack(' 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] - if previous_op_mode != DDEvents.UNKNOWN_STATE: - previous_sub_mode_enum_class = self._dd_op_mode_2_sub_mode[previous_op_mode] - event_data_1 = previous_sub_mode_enum_class(event_data_1).name - # Unknown previous state. Display value instead of name. + # Go through the Operation Status Change message list starting from the back + # Index description: + # 0: Timestamp + # 1: Op Mode + # 2: Sub Mode + # 3: State + for i in range(len(op_status_list) - 1, -1, -1): + op_status_msg = op_status_list[i] + + # Look for a match for event_data_2 + if not data_2_found: + if op_status_msg[2] == event_data_2: + event_2_op_mode = dd_enum_repository.DDOpModes(op_status_msg[1]).name + data_2_found = True + + # Look for a match for event_data_1 after event_data_2 is found + # Criteria is that the opmode, submode pair can't be the same as the one found for event_data_2 else: - event_data_1 = str(event_data_1) - event_data_2 = current_sub_mode_enum_class(event_data_2).name - else: + if op_status_msg[2] == event_data_1 and \ + (op_status_msg[2] != event_data_2 or dd_enum_repository.DDOpModes(op_status_msg[1]).name != event_2_op_mode): + event_1_op_mode = dd_enum_repository.DDOpModes(op_status_msg[1]).name + + # If op mode for event_data_2 found but not found for event_data_1 and run out of operation states + # assume it's the start of the unit start up and the going to standby is not logged yet + if event_2_op_mode != self.UNKNOWN_STATE and event_1_op_mode == self.UNKNOWN_STATE: + event_1_op_mode = dd_enum_repository.DDOpModes.MODE_STAN.name - 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._dd_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 - event_tuple = (datetime.now().astimezone().strftime('%Y-%m-%d %H:%M:%S.%f'), event_state_name, event_data_1, event_data_2) + # Update the event_data values + event_data_1 = self._op_mode_2_sub_mode[event_1_op_mode](event_data_1).name + event_data_2 = self._op_mode_2_sub_mode[event_2_op_mode](event_data_2).name + + # Update the tuple + event_tuple = (current_timestamp, event_enum.name, event_data_1, event_data_2) - elif event_state_name == dd_enum_repository.DDEventList.DD_EVENT_OPERATION_STATUS.name: - event_tuple = (time(), op_mode, sub_mode, sub_state) + elif event_enum is dd_enum_repository.DDEventList.DD_EVENT_OPERATION_STATUS: + event_tuple = (current_timestamp, op_mode, sub_mode, sub_state) # Update event dictionary - self._dd_event_dictionary[event_state_name].append(event_tuple) - self.dd_events_timestamp = timestamp + self._event_dictionary[event_enum.name].append(event_tuple) + self.events_timestamp = timestamp - @publish(["msg_id_dd_op_mode_data", "dd_event_op_mode_timestamp", "dd_event_op_mode", "dd_event_sub_mode"]) + + @publish(["msg_id_dd_op_mode_data", "event_op_mode_timestamp", "op_mode", "sub_mode"]) def _handler_dd_op_mode_sync(self, message, timestamp=0.0): """ Handles published DD operation mode messages. Current DD operation mode @@ -304,11 +301,10 @@ @param message: published DD operation mode broadcast message @return: None """ - mode = struct.unpack('i', bytearray( - message['message'][MsgFieldPositions.START_POS_FIELD_1:MsgFieldPositions.END_POS_FIELD_1])) - smode = struct.unpack('i', bytearray( - message['message'][MsgFieldPositions.START_POS_FIELD_2:MsgFieldPositions.END_POS_FIELD_2])) + msg_list = [] + msg_list.append(('self.op_mode', DataTypes.U32)) + msg_list.append(('self.sub_mode', DataTypes.U32)) - self.dd_event_op_mode = mode[0] - self.dd_event_sub_mode = smode[0] - self.dd_event_op_mode_timestamp = timestamp + self.process_into_vars(decoder_list = msg_list, + message = message) + self.event_op_mode_timestamp = timestamp