Index: leahi_dialin/dd/modules/records.py =================================================================== diff -u -r1e26622d7ef17d1f4d6587dbf62d6a2a561ebd18 -r5ebadf2099b379c5ee6d8417be5e3121c74611cf --- leahi_dialin/dd/modules/records.py (.../records.py) (revision 1e26622d7ef17d1f4d6587dbf62d6a2a561ebd18) +++ leahi_dialin/dd/modules/records.py (.../records.py) (revision 5ebadf2099b379c5ee6d8417be5e3121c74611cf) @@ -5,12 +5,12 @@ # 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 pressure_sensors.py +# @file records.py # # @author (last) Zoltan Miskolci -# @date (last) 04-May-2026 -# @author (original) Sean -# @date (original) 14-Apr-2020 +# @date (last) 22-Jun-2026 +# @author (original) Zoltan Miskolci +# @date (original) 04-May-2026 # ############################################################################ @@ -623,7 +623,7 @@ payload += byte_to_bytearray(c) payload += integer_to_bytearray(manufacturing_location) payload += integer_to_bytearray(manufacturing_date) - payload += unsigned_short_to_bytearray(crc16(payload)) + payload += unsigned_short_to_bytearray(self.crc16(payload)) return cmd_generic_override( payload = payload, @@ -657,7 +657,7 @@ payload += integer_to_bytearray(last_service_date) payload += integer_to_bytearray(service_interval_sec) payload += integer_to_bytearray(last_reset_time) - payload += unsigned_short_to_bytearray(crc16(payload)) + payload += unsigned_short_to_bytearray(self.crc16(payload)) return cmd_generic_override( payload = payload, @@ -784,7 +784,7 @@ # Calculate the CRC for the total institutional record (should be the last message) if record_enum == dd_enum_repository.InstitutionalRecordFields.CRC: - payload += unsigned_short_to_bytearray(crc16(total_payload)) + payload += unsigned_short_to_bytearray(self.crc16(total_payload)) # Else just send the data elif record_enum.datatype() in [DataTypes.U32, DataTypes.BOOL, DataTypes.S32]: @@ -846,7 +846,7 @@ payload += integer_to_bytearray(last_heat_active_cool_date) payload += integer_to_bytearray(last_filter_flush_date) payload += integer_to_bytearray(last_reset_time) - payload += unsigned_short_to_bytearray(crc16(payload)) + payload += unsigned_short_to_bytearray(self.crc16(payload)) return cmd_generic_override( payload = payload, @@ -880,7 +880,7 @@ payload += float_to_bytearray(gain) payload += float_to_bytearray(offset) payload += integer_to_bytearray(calibration_time) - payload += unsigned_short_to_bytearray(crc16(payload)) + payload += unsigned_short_to_bytearray(self.crc16(payload)) sensor_type = '' if msg_id == MsgIds.TBD: @@ -915,7 +915,7 @@ payload += float_to_bytearray(conductivity_uspcm) payload += float_to_bytearray(temperature_c) payload += integer_to_bytearray(calibration_time) - payload += unsigned_short_to_bytearray(crc16(payload)) + payload += unsigned_short_to_bytearray(self.crc16(payload)) conc_type = '' if msg_id == MsgIds.TBD: @@ -944,7 +944,7 @@ payload += float_to_bytearray(accel_y_offset) payload += float_to_bytearray(accel_z_offset) payload += integer_to_bytearray(calibration_time) - payload += unsigned_short_to_bytearray(crc16(payload)) + payload += unsigned_short_to_bytearray(self.crc16(payload)) conc_type = '' if msg_id == MsgIds.TBD: @@ -970,7 +970,7 @@ payload = float_to_bytearray(set_point) payload += integer_to_bytearray(calibration_time) - payload += unsigned_short_to_bytearray(crc16(payload)) + payload += unsigned_short_to_bytearray(self.crc16(payload)) return cmd_generic_override(payload = payload, reset = None, @@ -984,52 +984,3 @@ # ================================================= Private Methods ================================================= -CRC16_TABLE = [ - 0x0000, 0x1021, 0x2042, 0x3063, 0x4084, 0x50a5, 0x60c6, 0x70e7, - 0x8108, 0x9129, 0xa14a, 0xb16b, 0xc18c, 0xd1ad, 0xe1ce, 0xf1ef, - 0x1231, 0x0210, 0x3273, 0x2252, 0x52b5, 0x4294, 0x72f7, 0x62d6, - 0x9339, 0x8318, 0xb37b, 0xa35a, 0xd3bd, 0xc39c, 0xf3ff, 0xe3de, - 0x2462, 0x3443, 0x0420, 0x1401, 0x64e6, 0x74c7, 0x44a4, 0x5485, - 0xa56a, 0xb54b, 0x8528, 0x9509, 0xe5ee, 0xf5cf, 0xc5ac, 0xd58d, - 0x3653, 0x2672, 0x1611, 0x0630, 0x76d7, 0x66f6, 0x5695, 0x46b4, - 0xb75b, 0xa77a, 0x9719, 0x8738, 0xf7df, 0xe7fe, 0xd79d, 0xc7bc, - 0x48c4, 0x58e5, 0x6886, 0x78a7, 0x0840, 0x1861, 0x2802, 0x3823, - 0xc9cc, 0xd9ed, 0xe98e, 0xf9af, 0x8948, 0x9969, 0xa90a, 0xb92b, - 0x5af5, 0x4ad4, 0x7ab7, 0x6a96, 0x1a71, 0x0a50, 0x3a33, 0x2a12, - 0xdbfd, 0xcbdc, 0xfbbf, 0xeb9e, 0x9b79, 0x8b58, 0xbb3b, 0xab1a, - 0x6ca6, 0x7c87, 0x4ce4, 0x5cc5, 0x2c22, 0x3c03, 0x0c60, 0x1c41, - 0xedae, 0xfd8f, 0xcdec, 0xddcd, 0xad2a, 0xbd0b, 0x8d68, 0x9d49, - 0x7e97, 0x6eb6, 0x5ed5, 0x4ef4, 0x3e13, 0x2e32, 0x1e51, 0x0e70, - 0xff9f, 0xefbe, 0xdfdd, 0xcffc, 0xbf1b, 0xaf3a, 0x9f59, 0x8f78, - 0x9188, 0x81a9, 0xb1ca, 0xa1eb, 0xd10c, 0xc12d, 0xf14e, 0xe16f, - 0x1080, 0x00a1, 0x30c2, 0x20e3, 0x5004, 0x4025, 0x7046, 0x6067, - 0x83b9, 0x9398, 0xa3fb, 0xb3da, 0xc33d, 0xd31c, 0xe37f, 0xf35e, - 0x02b1, 0x1290, 0x22f3, 0x32d2, 0x4235, 0x5214, 0x6277, 0x7256, - 0xb5ea, 0xa5cb, 0x95a8, 0x8589, 0xf56e, 0xe54f, 0xd52c, 0xc50d, - 0x34e2, 0x24c3, 0x14a0, 0x0481, 0x7466, 0x6447, 0x5424, 0x4405, - 0xa7db, 0xb7fa, 0x8799, 0x97b8, 0xe75f, 0xf77e, 0xc71d, 0xd73c, - 0x26d3, 0x36f2, 0x0691, 0x16b0, 0x6657, 0x7676, 0x4615, 0x5634, - 0xd94c, 0xc96d, 0xf90e, 0xe92f, 0x99c8, 0x89e9, 0xb98a, 0xa9ab, - 0x5844, 0x4865, 0x7806, 0x6827, 0x18c0, 0x08e1, 0x3882, 0x28a3, - 0xcb7d, 0xdb5c, 0xeb3f, 0xfb1e, 0x8bf9, 0x9bd8, 0xabbb, 0xbb9a, - 0x4a75, 0x5a54, 0x6a37, 0x7a16, 0x0af1, 0x1ad0, 0x2ab3, 0x3a92, - 0xfd2e, 0xed0f, 0xdd6c, 0xcd4d, 0xbdaa, 0xad8b, 0x9de8, 0x8dc9, - 0x7c26, 0x6c07, 0x5c64, 0x4c45, 0x3ca2, 0x2c83, 0x1ce0, 0x0cc1, - 0xef1f, 0xff3e, 0xcf5d, 0xdf7c, 0xaf9b, 0xbfba, 0x8fd9, 0x9ff8, - 0x6e17, 0x7e36, 0x4e55, 0x5e74, 0x2e93, 0x3eb2, 0x0ed1, 0x1ef0 -] - - -# Constants (adjust if your original values differ) -INITIAL_CRC16_VAL = 0xFFFF # common default; change if needed -SHIFT_8_BITS = 8 -MASK_MSB = 0xFF - -def crc16(data: bytes) -> int: - crc = INITIAL_CRC16_VAL - - for byte in data: - index = byte ^ ((crc >> SHIFT_8_BITS) & MASK_MSB) - crc = ((crc << SHIFT_8_BITS) & 0xFFFF) ^ CRC16_TABLE[index] - - return crc & 0xFFFF Index: leahi_dialin/td/modules/records.py =================================================================== diff -u --- leahi_dialin/td/modules/records.py (revision 0) +++ leahi_dialin/td/modules/records.py (revision 5ebadf2099b379c5ee6d8417be5e3121c74611cf) @@ -0,0 +1,986 @@ +########################################################################### +# +# Copyright (c) 2020-2024 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 records.py +# +# @author (last) Zoltan Miskolci +# @date (last) 22-Jun-2026 +# @author (original) Zoltan Miskolci +# @date (original) 22-Jun-2026 +# +############################################################################ + +# Module imports +from logging import Logger +from functools import partial + +# Project imports +from leahi_dialin.common import td_enum_repository +from leahi_dialin.common.generic_defs import DataTypes +from leahi_dialin.common.msg_defs import MsgIds +from leahi_dialin.common.override_templates import cmd_generic_override +from leahi_dialin.protocols.CAN import CanMessenger, CanChannels +from leahi_dialin.utils.abstract_classes import AbstractSubSystem +from leahi_dialin.utils.base import publish +from leahi_dialin.utils.enums import DialinEnum +from leahi_dialin.utils.conversions import integer_to_bytearray, float_to_bytearray, byte_to_bytearray, unsigned_short_to_bytearray + + +class TDRecords(AbstractSubSystem): + """ + TD interface containing pressure related commands. + """ + + CALIB_RECORDS_PRESSURE_SENSORS = 'pressure_sensors' + CALIB_RECORDS_TEMPERATURE_SENSORS = 'temperature_sensors' + CALIB_RECORDS_CONCENTRATE_PUMPS = 'concentrate_pump' + CALIB_RECORDS_DIALYSATE_PUMPS = 'dialysate_pumps' + CALIB_RECORDS_ACID_CONCENTRATE = 'acid_concentrate' + CALIB_RECORDS_BICARB_CONCENTRATE = 'bicarb_concentrate' + CALIB_RECORDS_ACCELEROMETER = 'accelerometer' + CALIB_RECORDS_BLOOD_LEAK = 'blood_leak' + + def __init__(self, can_interface: CanMessenger, logger: Logger): + """ + @param can_interface: The CanMessenger object + """ + super().__init__() + self.can_interface = can_interface + self.logger = logger + + if self.can_interface is not None: + self.can_interface.register_receiving_publication_function(channel_id = CanChannels.td_sync_broadcast_ch_id, + message_id = MsgIds.MSG_ID_TD_NVM_SYSTEM_RECORD_RESPONSE.value, + function = self._handler_system_record_sync) + self.can_interface.register_receiving_publication_function(channel_id = CanChannels.td_sync_broadcast_ch_id, + message_id = MsgIds.MSG_ID_TD_NVM_SERVICE_RECORD_RESPONSE.value, + function = self._handler_service_record_sync) + self.can_interface.register_receiving_publication_function(channel_id = CanChannels.td_sync_broadcast_ch_id, + message_id = MsgIds.MSG_ID_TD_NVM_CAL_PRESSURE_SENSOR_RESPONSE.value, + function = self._handler_calibration_record_pres_sensor_sync) + self.can_interface.register_receiving_publication_function(channel_id = CanChannels.td_sync_broadcast_ch_id, + message_id = MsgIds.MSG_ID_TD_NVM_CAL_TEMP_SENSOR_RESPONSE.value, + function = self._handler_calibration_record_temp_sensor_sync) + self.can_interface.register_receiving_publication_function(channel_id = CanChannels.td_sync_broadcast_ch_id, + message_id = MsgIds.MSG_ID_TD_NVM_CAL_CONC_PUMP_RESPONSE.value, + function = self._handler_calibration_record_conc_pump_sync) + self.can_interface.register_receiving_publication_function(channel_id = CanChannels.td_sync_broadcast_ch_id, + message_id = MsgIds.MSG_ID_TD_NVM_CAL_D12_PUMP_RESPONSE.value, + function = self._handler_calibration_record_dial_pump_d12_sync) + self.can_interface.register_receiving_publication_function(channel_id = CanChannels.td_sync_broadcast_ch_id, + message_id = MsgIds.MSG_ID_TD_NVM_CAL_D48_PUMP_RESPONSE.value, + function = self._handler_calibration_record_dial_pump_d48_sync) + self.can_interface.register_receiving_publication_function(channel_id = CanChannels.td_sync_broadcast_ch_id, + message_id = MsgIds.MSG_ID_TD_NVM_CAL_ACID_CONCENTRATE_RESPONSE.value, + function = self._handler_calibration_record_acid_conc_sync) + self.can_interface.register_receiving_publication_function(channel_id = CanChannels.td_sync_broadcast_ch_id, + message_id = MsgIds.MSG_ID_TD_NVM_CAL_BICARB_CONCENTRATE_RESPONSE.value, + function = self._handler_calibration_record_bicarb_conc_sync) + self.can_interface.register_receiving_publication_function(channel_id = CanChannels.td_sync_broadcast_ch_id, + message_id = MsgIds.MSG_ID_TD_NVM_CAL_ACCEL_SENSOR_RESPONSE.value, + function = self._handler_calibration_record_accelerometer_sync) + self.can_interface.register_receiving_publication_function(channel_id = CanChannels.td_sync_broadcast_ch_id, + message_id = MsgIds.MSG_ID_TD_NVM_CAL_BLOOD_LEAK_SENSOR_RESPONSE.value, + function = self._handler_calibration_record_blood_leak_sync) + self.can_interface.register_receiving_publication_function(channel_id = CanChannels.td_sync_broadcast_ch_id, + message_id = MsgIds.MSG_ID_TD_NVM_INSTITUTIONAL_RECORD_RESPONSE.value, + function = self._handler_institutional_record_sync) + self.can_interface.register_receiving_publication_function(channel_id = CanChannels.td_sync_broadcast_ch_id, + message_id = MsgIds.MSG_ID_TD_NVM_USAGE_INFO_RECORD_RESPONSE.value, + function = self._handler_usage_info_record_sync) + + self.system_records_timestamp = 0 #: The timestamp of the latest System Records message + self.service_records_timestamp = 0 #: The timestamp of the latest Service Records message + self.calibration_records_timestamp = 0 #: The timestamp of the latest Calibration Records message + self.institutional_records_timestamp = 0 #: The timestamp of the latest Institutional Records message + self.usage_info_records_timestamp = 0 #: The timestamp of the latest Usage Information Records message + + self.system_records = { } #: The System Records data in dictionary format + self.service_records = { } #: The Service Records data in dictionary format + self.calibration_records = { } #: The Calibration Records data in dictionary format + self.institutional_records = { } #: The Institutional Records data in dictionary format + self.usage_info_records = { } #: The Usage Information Records data in dictionary format + + for sys_record in td_enum_repository.SystemRecordFields: + self.system_records[sys_record.name] = None + + for serv_record in td_enum_repository.ServiceRecordFields: + self.service_records[serv_record.name] = None + + for cal_record in td_enum_repository.CalibrationRecordFields: + self.calibration_records[cal_record.name] = None + + for inst_record in td_enum_repository.InstitutionalRecordFields: + self.institutional_records[inst_record.name] = None + + for usage_record in td_enum_repository.UsageInformationRecordFields: + self.usage_info_records[usage_record.name] = None + + +# ================================================= CAN Message Handler Methods ================================================= + @publish(["msg_id_td_nvm_send_system_record", "system_records", "system_records_timestamp"]) + def _handler_system_record_sync(self, message, timestamp = 0.0): + """ + Handles published TD System Record data messages. TD System Records are captured + for reference. + + @param message: published data message + @return: none + """ + record_list = [] + for service_member in td_enum_repository.ServiceRecordFields: + record_list.append((service_member.name, service_member.datatype())) + + self.process_into_dict(dict_to_update = self.service_records, + decoder_list = record_list, + message = message) + # Get the payload related values + msg_list =[] + msg_list.append(('current_page', DataTypes.U32)) + msg_list.append(('all_pages', DataTypes.U32)) + msg_list.append(('payload_count', DataTypes.U32)) + + result = self.process_into_vars(decoder_list = msg_list, + message = message) + + # Get the record data + record_list = [] + for i in range(0, result['payload_count']): + sys_record = td_enum_repository.SystemRecordFields(i + self.pager_system_record) + if sys_record.multichar_length > 1: + for j in range(0, sys_record.multichar_length - 1): + name = f'{sys_record.name}_{j}' + record_list.append((name, sys_record.datatype())) + else: + name = sys_record.name + record_list.append((name, sys_record.datatype())) + result_payload = self.process_into_vars(decoder_list = record_list, + message = message, + start_from_byte = len(msg_list) * DataTypes.U32.size()) + + # Saving data into System Records + self.system_records[td_enum_repository.SystemRecordFields.TOP_LEVEL_PN.name] = '' + self.system_records[td_enum_repository.SystemRecordFields.TOP_LEVEL_SN.name] = '' + self.system_records[td_enum_repository.SystemRecordFields.MFG_LOCATION.name] = 0 + self.system_records[td_enum_repository.SystemRecordFields.MFG_DATE.name] = 0 + self.system_records[td_enum_repository.SystemRecordFields.CRC.name] = 0 + for key in result_payload: + if key.startswith(td_enum_repository.SystemRecordFields.TOP_LEVEL_PN.name): + value = result_payload[key] + self.system_records[td_enum_repository.SystemRecordFields.TOP_LEVEL_PN.name] += value + elif key.startswith(td_enum_repository.SystemRecordFields.TOP_LEVEL_SN.name): + value = result_payload[key] + self.system_records[td_enum_repository.SystemRecordFields.TOP_LEVEL_SN.name] += value + else: + self.system_records[key] = result_payload[key] + + # Increase the pager by the payload count to know where the next message should resume, or reset when it's the last message + if result['current_page'] == result['all_pages']: + self.pager_system_record = 0 + else: + self.pager_system_record =+ result['payload_count'] + + self.system_records_timestamp = timestamp + + + @publish(["msg_id_td_nvm_send_service_record", "service_records", "service_records_timestamp"]) + def _handler_service_record_sync(self, message, timestamp = 0.0): + """ + Handles published TD Service Record data messages. TD Service Records are captured + for reference. + + @param message: published data message + @return: none + """ + record_list = [] + for member in td_enum_repository.ServiceRecordFields: + record_list.append((member.name, member.datatype())) + + self.process_into_dict(dict_to_update = self.service_records, + decoder_list = record_list, + message = message) + + self.service_records_timestamp = timestamp + + + @publish(["msg_id_td_nvm_send_cal_pressure_sensor", "calibration_records", "calibration_records_timestamp"]) + def _handler_calibration_record_pres_sensor_sync(self, message, timestamp = 0.0): + """ + Handles published TD Calibration Record Pressure Sensor Details data messages. + TD Calibration Records Pressure Sensor Details are captured for reference. + + @param message: published data message + @return: none + """ + # Get the sensor_id to identify the location of the payload + msg_list = [('sensor_id', DataTypes.U08)] + result = self.process_into_vars(decoder_list = msg_list, + message = message) + sensor_name = td_enum_repository.TDPressureSensorNames(result['sensor_id']).name + + record_list = [] + for member in td_enum_repository.CalibRecordSensorFields: + record_list.append((self.CALIB_RECORDS_PRESSURE_SENSORS, sensor_name, member.name, member.datatype())) + + self.process_into_dict(dict_to_update = self.calibration_records, + decoder_list = record_list, + message = message, + start_from_byte = len(msg_list) * DataTypes.U08.size()) + + self.calibration_records_timestamp = timestamp + + + @publish(["msg_id_td_nvm_send_cal_pressure_sensor", "calibration_records", "calibration_records_timestamp"]) + def _handler_calibration_record_temp_sensor_sync(self, message, timestamp = 0.0): + """ + Handles published TD Calibration Record Temperature Sensor Details data messages. + TD Calibration Records Temperature Sensor Details are captured for reference. + + @param message: published data message + @return: none + """ + # Get the sensor_id to identify the location of the payload + msg_list = [('sensor_id', DataTypes.U08)] + result = self.process_into_vars(decoder_list = msg_list, + message = message) + sensor_name = td_enum_repository.TDTemperatureSensorNames(result['sensor_id']).name + + record_list = [] + for member in td_enum_repository.CalibRecordSensorFields: + record_list.append((self.CALIB_RECORDS_TEMPERATURE_SENSORS, sensor_name, member.name, member.datatype())) + + self.process_into_dict(dict_to_update = self.calibration_records, + decoder_list = record_list, + message = message, + start_from_byte = len(msg_list) * DataTypes.U08.size()) + + self.calibration_records_timestamp = timestamp + + + @publish(["msg_id_td_nvm_send_cal_pressure_sensor", "calibration_records", "calibration_records_timestamp"]) + def _handler_calibration_record_conc_pump_sync(self, message, timestamp = 0.0): + """ + Handles published TD Calibration Record Concentrate Pump Details data messages. + TD Calibration Records Concentrate Pump Details are captured for reference. + + @param message: published data message + @return: none + """ + # Get the sensor_id to identify the location of the payload + msg_list = [('sensor_id', DataTypes.U08)] + result = self.process_into_vars(decoder_list = msg_list, + message = message) + sensor_name = td_enum_repository.TDConcentratePumpNames(result['sensor_id']).name + + record_list = [] + for member in td_enum_repository.CalibRecordSensorFields: + record_list.append((self.CALIB_RECORDS_CONCENTRATE_PUMPS, sensor_name, member.name, member.datatype())) + + self.process_into_dict(dict_to_update = self.calibration_records, + decoder_list = record_list, + message = message, + start_from_byte = len(msg_list) * DataTypes.U08.size()) + + self.calibration_records_timestamp = timestamp + + + @publish(["msg_id_td_nvm_send_cal_pressure_sensor", "calibration_records", "calibration_records_timestamp"]) + def _handler_calibration_record_dial_pump_d12_sync(self, message, timestamp = 0.0): + """ + Handles published TD Calibration Record Dialysate Pump D12 Details data messages. + TD Calibration Records Dialysate Pump D12 Details are captured for reference. + + @param message: published data message + @return: none + """ + record_list = [] + record_list.append((self.CALIB_RECORDS_DIALYSATE_PUMPS, td_enum_repository.TDDialysatePumpNames.D12_PUMP.name, 'target_speed', DataTypes.F32)) + for member in td_enum_repository.CalibRecordSensorFields: + record_list.append((self.CALIB_RECORDS_DIALYSATE_PUMPS, td_enum_repository.TDDialysatePumpNames.D12_PUMP.name, member.name, member.datatype())) + + self.process_into_dict(dict_to_update = self.calibration_records, + decoder_list = record_list, + message = message) + + self.calibration_records_timestamp = timestamp + + + @publish(["msg_id_td_nvm_send_cal_pressure_sensor", "calibration_records", "calibration_records_timestamp"]) + def _handler_calibration_record_dial_pump_d48_sync(self, message, timestamp = 0.0): + """ + Handles published TD Calibration Record Dialysate Pump D48 Details data messages. + TD Calibration Records Dialysate Pump D48 Details are captured for reference. + + @param message: published data message + @return: none + """ + record_list = [] + for member in td_enum_repository.CalibRecordSensorFields: + record_list.append((self.CALIB_RECORDS_DIALYSATE_PUMPS, td_enum_repository.TDDialysatePumpNames.D48_PUMP.name, member.name, member.datatype())) + + self.process_into_dict(dict_to_update = self.calibration_records, + decoder_list = record_list, + message = message) + + self.calibration_records_timestamp = timestamp + + + @publish(["msg_id_td_nvm_send_cal_pressure_sensor", "calibration_records", "calibration_records_timestamp"]) + def _handler_calibration_record_acid_conc_sync(self, message, timestamp = 0.0): + """ + Handles published TD Calibration Record Acid Concentrate Details data messages. + TD Calibration Records Acid Concentrate Details are captured for reference. + + @param message: published data message + @return: none + """ + record_list = [] + for member in td_enum_repository.CalibRecordConcentrateFields: + record_list.append((self.CALIB_RECORDS_ACID_CONCENTRATE, member.name, member.datatype())) + + self.process_into_dict(dict_to_update = self.calibration_records, + decoder_list = record_list, + message = message) + + self.calibration_records_timestamp = timestamp + + + @publish(["msg_id_td_nvm_send_cal_pressure_sensor", "calibration_records", "calibration_records_timestamp"]) + def _handler_calibration_record_bicarb_conc_sync(self, message, timestamp = 0.0): + """ + Handles published TD Calibration Record Bicarb Concentrate Details data messages. + TD Calibration Records Bicarb Concentrate Details are captured for reference. + + @param message: published data message + @return: none + """ + record_list = [] + for member in td_enum_repository.CalibRecordConcentrateFields: + record_list.append((self.CALIB_RECORDS_BICARB_CONCENTRATE, member.name, member.datatype())) + + self.process_into_dict(dict_to_update = self.calibration_records, + decoder_list = record_list, + message = message) + + self.calibration_records_timestamp = timestamp + + + @publish(["msg_id_td_nvm_send_cal_pressure_sensor", "calibration_records", "calibration_records_timestamp"]) + def _handler_calibration_record_accelerometer_sync(self, message, timestamp = 0.0): + """ + Handles published TD Calibration Record Accelerometer Details data messages. + TD Calibration Records Accelerometer Details are captured for reference. + + @param message: published data message + @return: none + """ + record_list = [] + for member in td_enum_repository.CalibRecordAccelerometerFields: + record_list.append((self.CALIB_RECORDS_ACCELEROMETER, member.name, member.datatype())) + + self.process_into_dict(dict_to_update = self.calibration_records, + decoder_list = record_list, + message = message) + + self.calibration_records_timestamp = timestamp + + + @publish(["msg_id_td_nvm_send_cal_pressure_sensor", "calibration_records", "calibration_records_timestamp"]) + def _handler_calibration_record_blood_leak_sync(self, message, timestamp = 0.0): + """ + Handles published TD Calibration Record Blood Leak Details data messages. + TD Calibration Records Blood Leak Details are captured for reference. + + @param message: published data message + @return: none + """ + record_list = [] + for member in td_enum_repository.CalibRecordBloodLeakFields: + record_list.append((self.CALIB_RECORDS_BLOOD_LEAK, member.name, member.datatype())) + + self.process_into_dict(dict_to_update = self.calibration_records, + decoder_list = record_list, + message = message) + + self.calibration_records_timestamp = timestamp + + + @publish(["msg_id_td_nvm_send_institutional_record", "institutional_records", "institutional_records_timestamp"]) + def _handler_institutional_record_sync(self, message, timestamp = 0.0): + """ + Handles published TD Institutional Record data messages. TD Institutional Records are captured + for reference. + + @param message: published data message + @return: none + """ + record_list = [('record_id', DataTypes.U08)] + results = self.process_into_vars(decoder_list = record_list, + message = message) + + record_id = int(results['record_id']) + member = td_enum_repository.InstitutionalRecordFields(record_id) + + record_list = [] + record_list.append((member.name, member.datatype())) + + self.process_into_dict(dict_to_update = self.institutional_records, + decoder_list = record_list, + message = message, + start_from_byte = len(record_list) * DataTypes.U08.size()) + + self.institutional_records_timestamp = timestamp + + + @publish(["msg_id_td_nvm_send_usage_info_record", "usage_info_records", "usage_info_records_timestamp"]) + def _handler_usage_info_record_sync(self, message, timestamp = 0.0): + """ + Handles published TD Usage Information Record data messages. TD Usage Information Records are captured + for reference. + + @param message: published data message + @return: none + """ + record_list = [] + for member in td_enum_repository.UsageInformationRecordFields: + record_list.append((member.name, member.datatype())) + + self.process_into_dict(dict_to_update = self.service_records, + decoder_list = record_list, + message = message) + + self.usage_info_records_timestamp = timestamp + + +# ================================================= Go to Service Mode Method ================================================= + def cmd_initiate_service_mode(self) -> int: + """ + Constructs and sends a request to change to Service operation mode via CAN bus. + Constraints: + Must be logged into TD. + Transition from current to requested op mode must be legal. + + @return: 1 if successful, zero otherwise + """ + service_mode = td_enum_repository.TDOpModes.MODE_SERV + + payload = integer_to_bytearray(service_mode.value) + + return cmd_generic_override( + payload = payload, + reset = None, + channel_id = CanChannels.dialin_to_td_ch_id, + msg_id = MsgIds.MSG_ID_TD_SET_OPERATION_MODE_OVERRIDE_REQUEST, + entity_name = 'TD Operation Mode', + override_text = f'set to {service_mode.name}', + logger = self.logger, + can_interface = self.can_interface) + + + +# ================================================= Request Records Methods ================================================= + def cmd_request_system_records(self) -> int: + """ + Constructs and sends a request for System Records. + Constraints: + Must be logged into TD. + Must be in Service mode. + + @return: 1 if successful, zero otherwise + """ + payload = byte_to_bytearray(td_enum_repository.RecordTypes.SYSTEM_RECORD.value) + + return cmd_generic_override( + payload = payload, + reset = None, + channel_id = CanChannels.dialin_to_td_ch_id, + msg_id = MsgIds.MSG_ID_UI_TD_NVM_GET_RECORD_REQUEST, + entity_name = f'TD System Record request', + override_text = '', + logger = self.logger, + can_interface = self.can_interface) + + + def cmd_request_service_records(self) -> int: + """ + Constructs and sends a request for Service Records. + Constraints: + Must be logged into TD. + Must be in Service mode. + + @return: 1 if successful, zero otherwise + """ + payload = byte_to_bytearray(td_enum_repository.RecordTypes.SERVICE_RECORD.value) + + return cmd_generic_override( + payload = payload, + reset = None, + channel_id = CanChannels.dialin_to_td_ch_id, + msg_id = MsgIds.MSG_ID_UI_TD_NVM_GET_RECORD_REQUEST, + entity_name = f'TD Service Record request', + override_text = '', + logger = self.logger, + can_interface = self.can_interface) + + + def cmd_request_calibration_records(self) -> int: + """ + Constructs and sends a request for Calibration Records. + Constraints: + Must be logged into TD. + Must be in Service mode. + + @return: 1 if successful, zero otherwise + """ + payload = byte_to_bytearray(td_enum_repository.RecordTypes.CALIBRATION_RECORD.value) + + return cmd_generic_override( + payload = payload, + reset = None, + channel_id = CanChannels.dialin_to_td_ch_id, + msg_id = MsgIds.MSG_ID_UI_TD_NVM_GET_RECORD_REQUEST, + entity_name = f'TD Calibration Record request', + override_text = '', + logger = self.logger, + can_interface = self.can_interface) + + + def cmd_request_institutional_records(self) -> int: + """ + Constructs and sends a request for Institutional Records. + Constraints: + Must be logged into TD. + Must be in Service mode. + + @return: 1 if successful, zero otherwise + """ + payload = byte_to_bytearray(td_enum_repository.RecordTypes.INSTITUTIONAL_RECORD.value) + + return cmd_generic_override( + payload = payload, + reset = None, + channel_id = CanChannels.dialin_to_td_ch_id, + msg_id = MsgIds.MSG_ID_UI_TD_NVM_GET_RECORD_REQUEST, + entity_name = f'TD Institutional Record request', + override_text = '', + logger = self.logger, + can_interface = self.can_interface) + + + def cmd_request_usage_information_records(self) -> int: + """ + Constructs and sends a request for Usage Information Records. + Constraints: + Must be logged into TD. + Must be in Service mode. + + @return: 1 if successful, zero otherwise + """ + payload = byte_to_bytearray(td_enum_repository.RecordTypes.USAGE_INFORMATION_RECORD.value) + + return cmd_generic_override( + payload = payload, + reset = None, + channel_id = CanChannels.dialin_to_td_ch_id, + msg_id = MsgIds.MSG_ID_UI_TD_NVM_GET_RECORD_REQUEST, + entity_name = f'TD Usage Information Record request', + override_text = '', + logger = self.logger, + can_interface = self.can_interface) + + + +# ================================================= Set Records Main Methods ================================================= + def cmd_set_system_records(self, + is_ro_featured: bool=False, + is_ro_featured_boost_pump: bool=False, + part_number: str='0000000000', + serial_number: str='00000000000000000000', + manufacturing_location: int=0, + manufacturing_date: int=0) -> int: + """ + Constructs and sends a command for setting the System Records. + Constraints: + Must be logged into TD. + Must be in Service mode. + + @return: 1 if successful, zero otherwise + """ + if len(part_number) != 10: + raise ValueError("part_number must be 10 characters") + if len(serial_number) != 10: + raise ValueError("serial must be 20 characters") + + payload = integer_to_bytearray(is_ro_featured) + payload = integer_to_bytearray(is_ro_featured_boost_pump) + for c in part_number: + payload += byte_to_bytearray(c) + for c in serial_number: + payload += byte_to_bytearray(c) + payload += integer_to_bytearray(manufacturing_location) + payload += integer_to_bytearray(manufacturing_date) + payload += unsigned_short_to_bytearray(self.crc16(payload)) + + return cmd_generic_override( + payload = payload, + reset = None, + channel_id = CanChannels.dialin_to_td_ch_id, + msg_id = MsgIds.MSG_ID_UI_TD_NVM_SET_SYSTEM_RECORD, + entity_name = f'New TD System Record', + override_text = 'being set', + logger = self.logger, + can_interface = self.can_interface) + + + def cmd_set_service_records(self, + is_hdf_online: bool=False, + water_recovery: int=0, + service_loc: int=0, + last_service_date: int=0, + service_interval_sec: int=0, + last_reset_time: int=0) -> int: + """ + Constructs and sends a command for setting the Service Records. + Constraints: + Must be logged into TD. + Must be in Service mode. + + @return: 1 if successful, zero otherwise + """ + payload = integer_to_bytearray(is_hdf_online) + payload += integer_to_bytearray(water_recovery) + payload += byte_to_bytearray(service_loc) + payload += integer_to_bytearray(last_service_date) + payload += integer_to_bytearray(service_interval_sec) + payload += integer_to_bytearray(last_reset_time) + payload += unsigned_short_to_bytearray(self.crc16(payload)) + + return cmd_generic_override( + payload = payload, + reset = None, + channel_id = CanChannels.dialin_to_td_ch_id, + msg_id = MsgIds.MSG_ID_TD_NVM_SET_SERVICE_RECORD, + entity_name = f'New TD Service Record', + override_text = 'being set', + logger = self.logger, + can_interface = self.can_interface) + + + def cmd_set_calibration_records(self, calibration_records: dict) -> int: + """ + Constructs and sends a command for setting the Calibration Records. + Constraints: + Must be logged into TD. + Must be in Service mode. + + @param calibration_records: (Dictionary) The stored and structured Calibration Records + @return: 1 if successful, zero otherwise + """ + msg_id_pairing = { + self.CALIB_RECORDS_PRESSURE_SENSORS: MsgIds.MSG_ID_TD_NVM_SET_CAL_PRESSURE_SENSOR, + self.CALIB_RECORDS_TEMPERATURE_SENSORS: MsgIds.MSG_ID_TD_NVM_SET_CAL_TEMP_SENSOR, + self.CALIB_RECORDS_CONCENTRATE_PUMPS: MsgIds.MSG_ID_TD_NVM_SET_CAL_D48_PUMP, + self.CALIB_RECORDS_DIALYSATE_PUMPS: MsgIds.MSG_ID_TD_NVM_SET_CAL_CONC_PUMP, + self.CALIB_RECORDS_ACID_CONCENTRATE: MsgIds.MSG_ID_TD_NVM_SET_CAL_ACID_CONCENTRATE, + self.CALIB_RECORDS_BICARB_CONCENTRATE: MsgIds.MSG_ID_TD_NVM_SET_CAL_BICARB_CONCENTRATE, + self.CALIB_RECORDS_ACCELEROMETER: MsgIds.MSG_ID_TD_NVM_SET_CAL_ACCEL_SENSOR, + self.CALIB_RECORDS_BLOOD_LEAK: MsgIds.MSG_ID_TD_NVM_SET_CAL_BLOOD_LEAK_SENSOR, + } + + # Make a dictionary to store all the send functions for later send + send_data = [] + for key in calibration_records: + if key.lower() in [self.CALIB_RECORDS_PRESSURE_SENSORS, self.CALIB_RECORDS_TEMPERATURE_SENSORS, self.CALIB_RECORDS_CONCENTRATE_PUMPS, self.CALIB_RECORDS_DIALYSATE_PUMPS]: + for sensor in calibration_records[key]: + # Exception handling as D12 has an extra field (target speed) + if sensor in [td_enum_repository.TDDialysatePumpNames.D12_PUMP]: + # Store the function but do not execute it + send_data.append(partial(self.cmd_set_calibration_records_sensor(sensor_enum = sensor, + msg_id = MsgIds.MSG_ID_TD_NVM_SET_CAL_D12_PUMP, + dialysate_pump_target_speed = calibration_records[key][sensor]['target_speed'], + forth_order_coeff = calibration_records[key][sensor]['forth_order_coeff'], + third_order_coeff = calibration_records[key][sensor]['third_order_coeff'], + second_order_coeff = calibration_records[key][sensor]['second_order_coeff'], + gain = calibration_records[key][sensor]['gain'], + offset = calibration_records[key][sensor]['offset'], + calibration_time = calibration_records[key][sensor]['calibration_time']))) + else: + # Store the function but do not execute it + send_data.append(partial(self.cmd_set_calibration_records_sensor(sensor_enum = sensor, + msg_id = msg_id_pairing[key], + forth_order_coeff = calibration_records[key][sensor]['forth_order_coeff'], + third_order_coeff = calibration_records[key][sensor]['third_order_coeff'], + second_order_coeff = calibration_records[key][sensor]['second_order_coeff'], + gain = calibration_records[key][sensor]['gain'], + offset = calibration_records[key][sensor]['offset'], + calibration_time = calibration_records[key][sensor]['calibration_time']))) + elif key.lower() in [self.CALIB_RECORDS_ACID_CONCENTRATE, self.CALIB_RECORDS_BICARB_CONCENTRATE]: + for sensor in calibration_records[key]: + # Store the function but do not execute it + send_data.append(partial(self.cmd_set_calibration_records_concentrate(msg_id = msg_id_pairing[key], + concentrate_mix_ratio = calibration_records[key][sensor]['concentrate_mix_ratio'], + volume_ml = calibration_records[key][sensor]['volume_ml'], + conductivity_uspcm = calibration_records[key][sensor]['conductivity_uspcm'], + temperature_c = calibration_records[key][sensor]['temperature_c'], + calibration_time = calibration_records[key][sensor]['calibration_time']))) + elif key.lower() in [self.CALIB_RECORDS_ACCELEROMETER]: + for sensor in calibration_records[key]: + # Store the function but do not execute it + send_data.append(partial(self.cmd_set_calibration_records_accelerometer(msg_id = msg_id_pairing[key], + accel_x_offset = calibration_records[key][sensor]['accel_x_offset'], + accel_y_offset = calibration_records[key][sensor]['accel_y_offset'], + accel_z_offset = calibration_records[key][sensor]['accel_z_offset'], + calibration_time = calibration_records[key][sensor]['calibration_time']))) + elif key.lower() in [self.CALIB_RECORDS_BLOOD_LEAK]: + for sensor in calibration_records[key]: + # Store the function but do not execute it + send_data.append(partial(self.cmd_set_calibration_records_blood_leak(msg_id = msg_id_pairing[key], + set_point = calibration_records[key][sensor]['set_point'], + calibration_time = calibration_records[key][sensor]['calibration_time']))) + + # Execute the stored functions one by one + # Remove the ones that got 1 (successfully recieved) as response + # Retry the ones that are failed 2 more times + retry = 0 + while send_data != [] and retry < 3: + failed = [] + for func in send_data: + resp = func() + if resp == 1: + failed.append(func) + send_data = failed + retry += 1 + + + def cmd_set_institutional_records(self, institutional_records: dict) -> int: + """ + Constructs and sends a command for setting the Institutional Records. + Constraints: + Must be logged into TD. + Must be in Service mode. + + @param institutional_records: (Dictionary) The stored and structured Institutional Records + @return: 1 if successful, zero otherwise + """ + # Make a dictionary to store all the send functions for later send + send_data = [] + total_payload = b'' + for record_enum in td_enum_repository.InstitutionalRecordFields: + if record_enum in institutional_records: + value = institutional_records[record_enum] + else: + # Set default values if it's missing from the provided dictionary + if record_enum.datatype() in [DataTypes.U32, DataTypes.BOOL, DataTypes.S32, DataTypes.U16, DataTypes.U08, DataTypes.BOOL_U08]: + value = 0 + elif record_enum.datatype() in [DataTypes.F32]: + value = 0.0 + + # Set the enum_id to identify which parameter is being sent U08 - 1 byte - 0->255 !! + payload = byte_to_bytearray(record_enum.value) + + # Calculate the CRC for the total institutional record (should be the last message) + if record_enum == td_enum_repository.InstitutionalRecordFields.CRC: + payload += unsigned_short_to_bytearray(self.crc16(total_payload)) + + # Else just send the data + elif record_enum.datatype() in [DataTypes.U32, DataTypes.BOOL, DataTypes.S32]: + payload += integer_to_bytearray(value) + total_payload += integer_to_bytearray(value) + elif record_enum.datatype() in [DataTypes.F32]: + payload += float_to_bytearray(value) + total_payload += float_to_bytearray(value) + elif record_enum.datatype() in [DataTypes.U16]: + payload += unsigned_short_to_bytearray(value) + total_payload += unsigned_short_to_bytearray(value) + elif record_enum.datatype() in [DataTypes.U08, DataTypes.BOOL_U08]: + payload += byte_to_bytearray(value) + total_payload += byte_to_bytearray(value) + + send_data.append(partial(cmd_generic_override(payload = payload, + reset = None, + channel_id = CanChannels.dialin_to_td_ch_id, + msg_id = MsgIds.MSG_ID_TD_NVM_SET_INSTITUTIONAL_RECORD, + entity_name = f'TD Institutional Record', + override_text = 'being set', + logger = self.logger, + can_interface = self.can_interface))) + + # Execute the stored functions one by one + # Remove the ones that got 1 (successfully recieved) as response + # Retry the ones that are failed 2 more times + retry = 0 + while send_data != [] and retry < 3: + failed = [] + for func in send_data: + resp = func() + if resp == 1: + failed.append(func) + send_data = failed + retry += 1 + + + def cmd_set_usage_info_records(self, + ro_water_gen_total_l: float=0.0, + ro_water_gen_since_last_serv_l: float=0.0, + last_basic_flush_date: int=0, + last_heat_disinfect_date: int=0, + last_heat_active_cool_date: int=0, + last_filter_flush_date: int=0, + last_reset_time: int=0) -> int: + """ + Constructs and sends a command for setting the Usage Information Records. + Constraints: + Must be logged into TD. + Must be in Service mode. + + @return: 1 if successful, zero otherwise + """ + payload = float_to_bytearray(ro_water_gen_total_l) + payload += float_to_bytearray(ro_water_gen_since_last_serv_l) + payload += integer_to_bytearray(last_basic_flush_date) + payload += integer_to_bytearray(last_heat_disinfect_date) + payload += integer_to_bytearray(last_heat_active_cool_date) + payload += integer_to_bytearray(last_filter_flush_date) + payload += integer_to_bytearray(last_reset_time) + payload += unsigned_short_to_bytearray(self.crc16(payload)) + + return cmd_generic_override( + payload = payload, + reset = None, + channel_id = CanChannels.dialin_to_td_ch_id, + msg_id = MsgIds.MSG_ID_TD_NVM_SET_USAGE_INFO_RECORD, + entity_name = f'New TD Usage Information Record', + override_text = 'being set', + logger = self.logger, + can_interface = self.can_interface) + + + +# ================================================= Set Records Support Methods ================================================= + def cmd_set_calibration_records_sensor(self, + sensor_enum: DialinEnum, + msg_id: MsgIds, + dialysate_pump_target_speed: float=0.0, + forth_order_coeff: float=0.0, + third_order_coeff: float=0.0, + second_order_coeff: float=0.0, + gain: float=0.0, + offset: float=0.0, + calibration_time: int = 0) -> int: + payload = integer_to_bytearray(sensor_enum.value) + if msg_id == MsgIds.TBD: + payload += float_to_bytearray(dialysate_pump_target_speed) + payload += float_to_bytearray(forth_order_coeff) + payload += float_to_bytearray(third_order_coeff) + payload += float_to_bytearray(second_order_coeff) + payload += float_to_bytearray(gain) + payload += float_to_bytearray(offset) + payload += integer_to_bytearray(calibration_time) + payload += unsigned_short_to_bytearray(self.crc16(payload)) + + sensor_type = '' + if msg_id == MsgIds.TBD: + sensor_type = 'Pressure Sensor' + elif msg_id == MsgIds.TBD: + sensor_type = 'Temperature Sensor' + elif msg_id == MsgIds.TBD: + sensor_type = 'Concentrate Pump' + elif msg_id == MsgIds.TBD: + sensor_type = 'Dialysate Pump' + + return cmd_generic_override(payload = payload, + reset = None, + channel_id = CanChannels.dialin_to_td_ch_id, + msg_id = msg_id, + entity_name = f'TD {sensor_type} {sensor_enum.name} Calibration Record', + override_text = 'being set', + logger = self.logger, + can_interface = self.can_interface) + + + def cmd_set_calibration_records_concentrate(self, + msg_id: MsgIds, + concentrate_mix_ratio: float=0.0, + volume_ml: float=0.0, + conductivity_uspcm: float=0.0, + temperature_c: float=0.0, + calibration_time: int = 0) -> int: + + payload = float_to_bytearray(concentrate_mix_ratio) + payload += float_to_bytearray(volume_ml) + payload += float_to_bytearray(conductivity_uspcm) + payload += float_to_bytearray(temperature_c) + payload += integer_to_bytearray(calibration_time) + payload += unsigned_short_to_bytearray(self.crc16(payload)) + + conc_type = '' + if msg_id == MsgIds.TBD: + conc_type = 'Acid' + elif msg_id == MsgIds.TBD: + conc_type = 'Bicarb' + + return cmd_generic_override(payload = payload, + reset = None, + channel_id = CanChannels.dialin_to_td_ch_id, + msg_id = msg_id, + entity_name = f'TD {conc_type} Concentrate Calibration Record', + override_text = 'being set', + logger = self.logger, + can_interface = self.can_interface) + + + def cmd_set_calibration_records_accelerometer(self, + msg_id: MsgIds, + accel_x_offset: float=0.0, + accel_y_offset: float=0.0, + accel_z_offset: float=0.0, + calibration_time: int = 0) -> int: + + payload = float_to_bytearray(accel_x_offset) + payload += float_to_bytearray(accel_y_offset) + payload += float_to_bytearray(accel_z_offset) + payload += integer_to_bytearray(calibration_time) + payload += unsigned_short_to_bytearray(self.crc16(payload)) + + conc_type = '' + if msg_id == MsgIds.TBD: + conc_type = 'Acid' + elif msg_id == MsgIds.TBD: + conc_type = 'Bicarb' + + return cmd_generic_override(payload = payload, + reset = None, + channel_id = CanChannels.dialin_to_td_ch_id, + msg_id = msg_id, + entity_name = f'TD {conc_type} Concentrate Calibration Record', + override_text = 'being set', + logger = self.logger, + can_interface = self.can_interface) + + + + def cmd_set_calibration_records_blood_leak(self, + msg_id: MsgIds, + set_point: float=0.0, + calibration_time: int = 0) -> int: + + payload = float_to_bytearray(set_point) + payload += integer_to_bytearray(calibration_time) + payload += unsigned_short_to_bytearray(self.crc16(payload)) + + return cmd_generic_override(payload = payload, + reset = None, + channel_id = CanChannels.dialin_to_td_ch_id, + msg_id = msg_id, + entity_name = f'TD Blood Leak Calibration Record', + override_text = 'being set', + logger = self.logger, + can_interface = self.can_interface) + + + +# ================================================= Private Methods ================================================= Index: leahi_dialin/td/treatment_delivery.py =================================================================== diff -u -r6c90336ac2cd8cf34ac620cff431a847d9ddf557 -r5ebadf2099b379c5ee6d8417be5e3121c74611cf --- leahi_dialin/td/treatment_delivery.py (.../treatment_delivery.py) (revision 6c90336ac2cd8cf34ac620cff431a847d9ddf557) +++ leahi_dialin/td/treatment_delivery.py (.../treatment_delivery.py) (revision 5ebadf2099b379c5ee6d8417be5e3121c74611cf) @@ -28,6 +28,7 @@ from .modules.ejector import TDEjector from .modules.events import TDEvents from .modules.pressure_sensors import TDPressureSensors +from .modules.records import TDRecords from .modules.switches import TDSwitches from .modules.syringe_pump import TDSyringePump from .modules.temperature_sensors import TDTemperatureSensors @@ -134,6 +135,7 @@ self.ejector = TDEjector(self.can_interface, self.logger) #: The Ejector module self.events = TDEvents(self.can_interface, self.logger) #: The Events module self.pressure_sensors = TDPressureSensors(self.can_interface, self.logger) #: The Pressure Sensors module + self.records = TDRecords(self.can_interface, self.logger) #: The Records module self.switches = TDSwitches(self.can_interface, self.logger) #: The Switches module self.syringe_pump = TDSyringePump(self.can_interface, self.logger) #: The Syringe Pump module self.temperature_sensors = TDTemperatureSensors(self.can_interface, self.logger) #: The Temperature Sensors module Index: leahi_dialin/utils/abstract_classes.py =================================================================== diff -u -r87ffed5716c48332d6dc3c5f5a80660614d813c0 -r5ebadf2099b379c5ee6d8417be5e3121c74611cf --- leahi_dialin/utils/abstract_classes.py (.../abstract_classes.py) (revision 87ffed5716c48332d6dc3c5f5a80660614d813c0) +++ leahi_dialin/utils/abstract_classes.py (.../abstract_classes.py) (revision 5ebadf2099b379c5ee6d8417be5e3121c74611cf) @@ -169,3 +169,59 @@ print('Finished cycle\n') if debug: print('done\n') + + + def crc16(data: bytes) -> int: + """ + Calculate a 16bit CRC for a payload. + + @data: (Bytes) The payload it needs CRC. + @return: (Integer) The CRC value + """ + CRC16_TABLE = [ + 0x0000, 0x1021, 0x2042, 0x3063, 0x4084, 0x50a5, 0x60c6, 0x70e7, + 0x8108, 0x9129, 0xa14a, 0xb16b, 0xc18c, 0xd1ad, 0xe1ce, 0xf1ef, + 0x1231, 0x0210, 0x3273, 0x2252, 0x52b5, 0x4294, 0x72f7, 0x62d6, + 0x9339, 0x8318, 0xb37b, 0xa35a, 0xd3bd, 0xc39c, 0xf3ff, 0xe3de, + 0x2462, 0x3443, 0x0420, 0x1401, 0x64e6, 0x74c7, 0x44a4, 0x5485, + 0xa56a, 0xb54b, 0x8528, 0x9509, 0xe5ee, 0xf5cf, 0xc5ac, 0xd58d, + 0x3653, 0x2672, 0x1611, 0x0630, 0x76d7, 0x66f6, 0x5695, 0x46b4, + 0xb75b, 0xa77a, 0x9719, 0x8738, 0xf7df, 0xe7fe, 0xd79d, 0xc7bc, + 0x48c4, 0x58e5, 0x6886, 0x78a7, 0x0840, 0x1861, 0x2802, 0x3823, + 0xc9cc, 0xd9ed, 0xe98e, 0xf9af, 0x8948, 0x9969, 0xa90a, 0xb92b, + 0x5af5, 0x4ad4, 0x7ab7, 0x6a96, 0x1a71, 0x0a50, 0x3a33, 0x2a12, + 0xdbfd, 0xcbdc, 0xfbbf, 0xeb9e, 0x9b79, 0x8b58, 0xbb3b, 0xab1a, + 0x6ca6, 0x7c87, 0x4ce4, 0x5cc5, 0x2c22, 0x3c03, 0x0c60, 0x1c41, + 0xedae, 0xfd8f, 0xcdec, 0xddcd, 0xad2a, 0xbd0b, 0x8d68, 0x9d49, + 0x7e97, 0x6eb6, 0x5ed5, 0x4ef4, 0x3e13, 0x2e32, 0x1e51, 0x0e70, + 0xff9f, 0xefbe, 0xdfdd, 0xcffc, 0xbf1b, 0xaf3a, 0x9f59, 0x8f78, + 0x9188, 0x81a9, 0xb1ca, 0xa1eb, 0xd10c, 0xc12d, 0xf14e, 0xe16f, + 0x1080, 0x00a1, 0x30c2, 0x20e3, 0x5004, 0x4025, 0x7046, 0x6067, + 0x83b9, 0x9398, 0xa3fb, 0xb3da, 0xc33d, 0xd31c, 0xe37f, 0xf35e, + 0x02b1, 0x1290, 0x22f3, 0x32d2, 0x4235, 0x5214, 0x6277, 0x7256, + 0xb5ea, 0xa5cb, 0x95a8, 0x8589, 0xf56e, 0xe54f, 0xd52c, 0xc50d, + 0x34e2, 0x24c3, 0x14a0, 0x0481, 0x7466, 0x6447, 0x5424, 0x4405, + 0xa7db, 0xb7fa, 0x8799, 0x97b8, 0xe75f, 0xf77e, 0xc71d, 0xd73c, + 0x26d3, 0x36f2, 0x0691, 0x16b0, 0x6657, 0x7676, 0x4615, 0x5634, + 0xd94c, 0xc96d, 0xf90e, 0xe92f, 0x99c8, 0x89e9, 0xb98a, 0xa9ab, + 0x5844, 0x4865, 0x7806, 0x6827, 0x18c0, 0x08e1, 0x3882, 0x28a3, + 0xcb7d, 0xdb5c, 0xeb3f, 0xfb1e, 0x8bf9, 0x9bd8, 0xabbb, 0xbb9a, + 0x4a75, 0x5a54, 0x6a37, 0x7a16, 0x0af1, 0x1ad0, 0x2ab3, 0x3a92, + 0xfd2e, 0xed0f, 0xdd6c, 0xcd4d, 0xbdaa, 0xad8b, 0x9de8, 0x8dc9, + 0x7c26, 0x6c07, 0x5c64, 0x4c45, 0x3ca2, 0x2c83, 0x1ce0, 0x0cc1, + 0xef1f, 0xff3e, 0xcf5d, 0xdf7c, 0xaf9b, 0xbfba, 0x8fd9, 0x9ff8, + 0x6e17, 0x7e36, 0x4e55, 0x5e74, 0x2e93, 0x3eb2, 0x0ed1, 0x1ef0 + ] + + # Constants (adjust if your original values differ) + INITIAL_CRC16_VAL = 0xFFFF # common default; change if needed + SHIFT_8_BITS = 8 + MASK_MSB = 0xFF + + crc = INITIAL_CRC16_VAL + + for byte in data: + index = byte ^ ((crc >> SHIFT_8_BITS) & MASK_MSB) + crc = ((crc << SHIFT_8_BITS) & 0xFFFF) ^ CRC16_TABLE[index] + + return crc & 0xFFFF