Index: dialin/common/msg_ids.py =================================================================== diff -u -r32e628abcbbd3fd70866505d9f2836a6f732ef06 -rcc1292b0d75e7a3fc1cbd0e45610805af4790108 --- dialin/common/msg_ids.py (.../msg_ids.py) (revision 32e628abcbbd3fd70866505d9f2836a6f732ef06) +++ dialin/common/msg_ids.py (.../msg_ids.py) (revision cc1292b0d75e7a3fc1cbd0e45610805af4790108) @@ -385,7 +385,12 @@ MSG_ID_HD_VALVES_STATES_PUBLISH_INTERVAL_OVERRIDE = 0x809A MSG_ID_HD_CAN_RECEIVE_ACK_MESSAGE_OVERRIDE = 0x809B MSG_ID_HD_RECIRULATION_PCT_OVERRIDE = 0x809C + MSG_ID_HD_RAW_AIR_TRAP_LEVEL_SENSOR_OVERRIDE = 0x809D + MSG_ID_HD_GET_INSTITUTIONAL_RECORD = 0x809E + MSG_ID_HD_SET_INSTITUTIONAL_RECORD = 0x809F + MSG_ID_HD_SEND_INSTITUTIONAL_RECORD = 0x80A0 + MSG_ID_DG_TESTER_LOGIN_REQUEST = 0xA000 MSG_ID_DG_ALARM_STATE_OVERRIDE = 0xA001 MSG_ID_DG_WATCHDOG_TASK_CHECKIN_OVERRIDE = 0xA002 Index: dialin/hd/institutional_record.py =================================================================== diff -u --- dialin/hd/institutional_record.py (revision 0) +++ dialin/hd/institutional_record.py (revision cc1292b0d75e7a3fc1cbd0e45610805af4790108) @@ -0,0 +1,330 @@ + +import struct +import time +from collections import OrderedDict +from enum import unique +from logging import Logger +from time import sleep +from ..common.msg_defs import MsgIds, MsgFieldPositions +from ..protocols.CAN import DenaliMessage, DenaliChannels +from ..utils.base import AbstractSubSystem, publish, DialinEnum +from ..utils.nv_ops_utils import NVOpsUtils, NVUtilsObserver, NVRecordsHD +from ..utils.conversions import integer_to_bytearray + + +class HDInstitutionalNVRecords(AbstractSubSystem): + """ + + Hemodialysis Device (HD) Dialin API sub-class for institutional record commands. + """ + + # The default service time interval is 6 months in seconds + _DEFAULT_SERVICE_INTERVAL_S = 15768000 + _RECORD_SPECS_BYTES = 12 + _DEFAULT_SERVICE_LOCATION = ServiceLocation.SERVICE_LOCATION_FACTORY.value + # TODO bring in the default values + + _DEFAULT_TIME_VALUE = 0 + _DEFAULT_CRC_VALUE = 0 + _FIRMWARE_STACK_NAME = 'HD' + + # Maximum allowed bytes that are allowed to be written to EEPROM in firmware + # The padding size then is calculated to be divisions of 16 + _EEPROM_MAX_BYTES_TO_WRITE = 16 + + # Delay in between each payload transfer + _PAYLOAD_TRANSFER_DELAY_S = 0.2 + _DIALIN_RECORD_UPDATE_DELAY_S = 0.2 + + def __init__(self, can_interface, logger: Logger): + """ + + @param can_interface: Denali CAN Messenger object + """ + + super().__init__() + + self.can_interface = can_interface + self.logger = logger + self._current_message = 0 + self._total_messages = 0 + self._received_msg_length = 0 + self._is_getting_institutional_in_progress = False + self._write_fw_data_to_excel = True + self._institutional_data = 0 + self._raw_institutional_record = [] + self._utilities = NVOpsUtils(logger=self.logger) + self.hd_institutional_record = self._prepare_hd_service_record() + + if self.can_interface is not None: + channel_id = DenaliChannels.hd_to_dialin_ch_id + msg_id = MsgIds.MSG_ID_HD_SEND_INSTITUTIONAL_RECORD.value + self.can_interface.register_receiving_publication_function(channel_id, msg_id, + self._handler_hd_institutional_sync) + self.hd_institutional_record_timestamp = 0.0 + HERE + def cmd_reset_hd_service_record(self) -> bool: + """ + Handles resetting HD service record. + + @return: True if successful, False otherwise + """ + self.hd_institutional_record = self._prepare_hd_service_record() + self.hd_institutional_record = self._utilities.reset_fw_system_service_record(self.hd_institutional_record) + status = self.cmd_set_hd_service_record(self.hd_institutional_record) + + return status + + def cmd_request_hd_service_record(self) -> int: + """ + Handles getting HD service record from firmware. + + @return: 1 upon success, False otherwise + """ + if self._is_getting_institutional_in_progress is not True: + self._is_getting_institutional_in_progress = True + # Clear the list for the next call + self._raw_institutional_record.clear() + + message = DenaliMessage.build_message(channel_id=DenaliChannels.dialin_to_hd_ch_id, + message_id=MsgIds.MSG_ID_HD_GET_SERVICE_RECORD.value) + + self.logger.debug('Getting HD service record') + + received_message = self.can_interface.send(message) + + # If there is content... + if received_message is not None: + # response payload is OK or not OK + return received_message['message'][DenaliMessage.PAYLOAD_START_INDEX] + else: + self.logger.debug("Timeout!!!!") + return False + + self.logger.debug("Request cancelled: an existing request is in progress.") + return False + + def cmd_hd_service_record_crc_override(self, crc: int) -> bool: + """ + Handles setting HD service_record CRC override. + + @param crc: (int) the CRC override value + + @return: True if successful, False otherwise + """ + # This command does not have a reset but since the corresponding payload structure in firmware requires a reset + # so the payload length is the same when it is received in the firmware. + reset_byte_array = integer_to_bytearray(0) + crc_value = integer_to_bytearray(crc) + hd_record = integer_to_bytearray(NVRecordsHD.NVDATAMGMT_SERVICE_RECORD.value) + payload = reset_byte_array + crc_value + hd_record + + message = DenaliMessage.build_message(channel_id=DenaliChannels.dialin_to_hd_ch_id, + message_id=MsgIds.MSG_ID_HD_NV_RECORD_CRC_OVERRIDE.value, + payload=payload) + + self.logger.debug("Overriding HD service record CRC to: " + str(crc)) + + # Send message + received_message = self.can_interface.send(message) + + # If there is content... + if received_message is not None: + # response payload is OK or not OK + return received_message['message'][DenaliMessage.PAYLOAD_START_INDEX] + else: + self.logger.error("Timeout!!!!") + return False + + def _handler_hd_institutional_sync(self, message, timestamp=0.0): + """ + Handles published HD system record messages. HD system records are captured for + processing and updating the HD system record. + + @param message: published HD system record data message + @return: None + """ + curr = struct.unpack('i', bytearray( + message['message'][MsgFieldPositions.START_POS_FIELD_1:MsgFieldPositions.END_POS_FIELD_1]))[0] + total = struct.unpack('i', bytearray( + message['message'][MsgFieldPositions.START_POS_FIELD_2:MsgFieldPositions.END_POS_FIELD_2]))[0] + length = struct.unpack('i', bytearray( + message['message'][MsgFieldPositions.START_POS_FIELD_3:MsgFieldPositions.END_POS_FIELD_3]))[0] + + self._current_message = curr + self._total_messages = total + self._received_msg_length = length + # The end of calibration_record record payload is from the start index + 12 bytes for the current message +total + # messages + the length of calibration_record. The rest is the CAN messaging CRC that is not needed + # to be kept + end_of_data_index = MsgFieldPositions.START_POS_FIELD_1 + self._RECORD_SPECS_BYTES + self._received_msg_length + + # Get the data only and not specs of it (i.e current message number) + self._institutional_data = message['message'][MsgFieldPositions.START_POS_FIELD_1:end_of_data_index] + + # Continue getting calibration_record records until the all the calibration_record messages are received. + # Concatenate the calibration_record records to each other + if self._current_message <= self._total_messages: + self._raw_institutional_record += (message['message'][MsgFieldPositions.START_POS_FIELD_1 + + self._RECORD_SPECS_BYTES:end_of_data_index]) + if self._current_message == self._total_messages: + # Done with receiving the messages + self._is_getting_institutional_in_progress = False + # If all the messages have been received, call another function to process the raw data + self._utilities.process_received_record_from_fw(self.hd_institutional_record, self._raw_institutional_record) + self.hd_institutional_record_timestamp = timestamp + self._handler_received_complete_hd_service_record() + + + @publish(["hd_service_record_timestamp","hd_service_record"]) + def _handler_received_complete_hd_service_record(self): + """ + Publishes the received service record + + @return: None + """ + self.logger.debug("Received a complete hd service record.") + + def cmd_set_hd_service_record(self, hd_service_record: OrderedDict) -> bool: + """ + Handles updating the HD system and sends it to FW. + + @param hd_service_record: (OrderedDict) the hd service record to be sent + @return: True upon success, False otherwise + """ + transfer_status = 1 + self.logger.debug('Setting HD service record') + + record_packets = self._utilities.prepare_record_to_send_to_fw(hd_service_record) + + # Update all the data packets with the last message count since is the number of messages that firmware + # should receive + for packet in record_packets: + # Sleep to let the firmware receive and process the data + time.sleep(self._PAYLOAD_TRANSFER_DELAY_S) + + # Convert the list packet to a bytearray + payload = b''.join(packet) + + message = DenaliMessage.build_message(channel_id=DenaliChannels.dialin_to_hd_ch_id, + message_id=MsgIds.MSG_ID_HD_SET_SERVICE_RECORD.value, + payload=payload) + + received_message = self.can_interface.send(message) + + # If there is no content... + if received_message is None: + self.logger.warning("HD ACK not received!") + continue + elif transfer_status == 0: + self.logger.debug("Sending HD service record failed") + return False + + transfer_status = received_message['message'][6] + + if transfer_status == 1: + self.logger.debug("Finished sending HD service record.") + return True + + def _prepare_hd_service_record(self): + """ + Handles assembling the sub dictionaries of each group to make the main HD service record. + + @return: (OrderedDict) an assembled hd service record + """ + result = OrderedDict() + groups_byte_size = 0 + # create a list of the functions of the sub dictionaries + functions = [self._prepare_service_record()] + + for function in functions: + # Update the groups bytes size so far to be use to padding later + groups_byte_size += function[1] + # Update the calibration record + result.update(function[0]) + + # Build the CRC of the main calibration_record record + record_crc = OrderedDict({'crc': [' bool: + """ + Handles setting the service record data that is in an excel report to the firmware. + + @param report_address: (str) the address in which its data must be written from excel + + @return: none + """ + + # Request the DG service record and set and observer class to callback when the record is read back + self.cmd_request_hd_service_record() + observer = NVUtilsObserver("hd_service_record") + # Attach the observer to the list + self.attach(observer) + while not observer.received: + sleep(0.1) + self._utilities.write_excel_record_to_fw_record(self.hd_institutional_record, report_address, + self._utilities.SERVICE_RECORD_TAB_NAME) + + ret = self.cmd_set_hd_service_record(self.hd_institutional_record) + return ret + + def cmd_get_hd_service_record(self, report_address: str = None): + """ + Publicly accessible function to request the HD service record and write the record to excel. + + @param report_address: the address that the report needs to be written to. The default is None so it picks an + address and writes the excel report. + + @return: none + """ + + # Create the excel report + self._utilities.prepare_excel_report(self._FIRMWARE_STACK_NAME, self._utilities.SERVICE_RECORD_TAB_NAME, + report_address, protect_sheet=True) + + # Create an object of the observer class to observe the dictionary + observer = NVUtilsObserver("hd_service_record") + # Attach the observer to the list + self.attach(observer) + + # Request the latest software configuration record from firmware + self.cmd_request_hd_service_record() + # Wait until data has been received from firmware + while not observer.received: + sleep(0.1) + # Write the updated values from excel to firmware + self._utilities.write_fw_record_to_excel(self.hd_institutional_record) Index: dialin/hd/service_record.py =================================================================== diff -u -r32e628abcbbd3fd70866505d9f2836a6f732ef06 -rcc1292b0d75e7a3fc1cbd0e45610805af4790108 --- dialin/hd/service_record.py (.../service_record.py) (revision 32e628abcbbd3fd70866505d9f2836a6f732ef06) +++ dialin/hd/service_record.py (.../service_record.py) (revision cc1292b0d75e7a3fc1cbd0e45610805af4790108) @@ -46,8 +46,9 @@ _DEFAULT_CRC_VALUE = 0 _FIRMWARE_STACK_NAME = 'HD' - # Maximum allowed bytes to be written to RTC RAM - _RTC_RAM_MAX_BYTES_TO_WRITE = 64 + # Maximum allowed bytes that are allowed to be written to EEPROM in firmware + # The padding size then is calculated to be divisions of 16 + _EEPROM_MAX_BYTES_TO_WRITE = 16 # Delay in between each payload transfer _PAYLOAD_TRANSFER_DELAY_S = 0.2