Index: leahi_dialin/common/generic_defs.py =================================================================== diff -u -rb167367cb2256f8b4a35bc2c9cde6cde2129d05d -r4c11f61e1e328407ed98a1b81a3e8b0adb278bd5 --- leahi_dialin/common/generic_defs.py (.../generic_defs.py) (revision b167367cb2256f8b4a35bc2c9cde6cde2129d05d) +++ leahi_dialin/common/generic_defs.py (.../generic_defs.py) (revision 4c11f61e1e328407ed98a1b81a3e8b0adb278bd5) @@ -17,27 +17,31 @@ from enum import unique from ..utils.enums import DialinEnum +from .dd_defs import DDPressureSensorNames, DDTemperatureSensorNames, DDConcentratePumpNames, DDDialysatePumpNames +from .disp_defs import AcidTypes, BicarbTypes +from .fp_defs import FPPressureSensorNames +# ================================================== Enum Creators: DataTypes ================================================== @unique class DataTypes(DialinEnum): - NONE = (0, ' Integer - S32 = (2, ' Integer - F32 = (3, ' Float - BOOL = (4, ' Integer + NONE = (0, ' Integer + S32 = (2, ' Integer + F32 = (3, ' Float + BOOL = (4, ' Integer # After this point the Data Types are extra ones used only in Dialin - U08 = (5, ' Integer - U16 = (6, ' Integer - S16 = (7, ' Integer - BOOL_U08 = (8, ' Integer - NUM_OF_DATA_TYPES = (9, None, 0) # Number of Data Types - None + U08 = (5, ' Integer + U16 = (6, ' Integer + S16 = (7, ' Integer + BOOL_U08 = (8, ' Integer + NUM_OF_DATA_TYPES = (9, None, 0) # Number of Data Types - None def __new__(cls, value, unpack_fmt, unpack_size): obj = object.__new__(cls) - obj._value_ = value # keeps .value as int - obj.unpack_fmt = unpack_fmt # attach extra attribute - obj.unpack_size = unpack_size # attach extra attribute + obj._value_ = value # keeps .value as int + obj.unpack_fmt = unpack_fmt # attach extra attribute + obj.unpack_size = unpack_size # attach extra attribute return obj def unpack_attrib(self) -> str: @@ -159,3 +163,347 @@ 'END_TREATMENT': [], 'NUM_OF_TD_ALARM_RESPONSE_BUTTONS': [] } + + + +# ================================================== Enum Creators: Record Base ================================================== +@unique +class RecordsBaseEnum(DialinEnum): + def __new__(cls, value, unpack_type, unpack_length: int=1): + obj = object.__new__(cls) + obj._value_ = value # keeps .value as int + obj.unpack_type = unpack_type # attach extra attribute + obj.unpack_length = unpack_length # attach extra attribute + return obj + + def datatype(self) -> DataTypes: + return self.unpack_type + + def multichar_length(self) -> int: + return self.unpack_length + + + +# ================================================== Enum Creators: Record Sub Fields ================================================== +@unique +class CalibRecordAccelerometerFields(RecordsBaseEnum): + ACCEL_X_OFFSET = (0, DataTypes.F32) # DD accelerometer X axis offset + ACCEL_Y_OFFSET = (1, DataTypes.F32) # DD accelerometer Y axis offset + ACCEL_Z_OFFSET = (2, DataTypes.F32) # DD accelerometer Z axis offset + CALIBRATION_TIME = (3, DataTypes.U32) # Calibration time + CRC = (4, DataTypes.U16) # CRC + NUM_OF_CALIBRATION_RECORD_ACCELEROMETER_FIELDS = (5, DataTypes.NONE) # Number of Calibration Record Accelerometer's Fields + +CalibRecordAccelerometerFields._str_list = { + # Official Name : Accepted strings + 'ACCEL_X_OFFSET': [], + 'ACCEL_Y_OFFSET': [], + 'ACCEL_Z_OFFSET': [], + 'CALIBRATION_TIME': [], + 'CRC': [], + 'NUM_OF_CALIBRATION_RECORD_ACCELEROMETER_FIELDS': [], +} + + +@unique +class CalibRecordBloodLeakFields(RecordsBaseEnum): + SET_POINT = (0, DataTypes.U16) # DD Blood leak sensor set point + CALIBRATION_TIME = (3, DataTypes.U32) # Calibration time + CRC = (4, DataTypes.U16) # CRC + NUM_OF_CALIBRATION_RECORD_BLOOD_LEAK_FIELDS = (5, DataTypes.NONE) # Number of Calibration Record Accelerometer's Fields + +CalibRecordBloodLeakFields._str_list = { + # Official Name : Accepted strings + 'SET_POINT': [], + 'CALIBRATION_TIME': [], + 'CRC': [], + 'NUM_OF_CALIBRATION_RECORD_BLOOD_LEAK_FIELDS': [], +} + + +@unique +class CalibRecordConcentrateFields(RecordsBaseEnum): + CONCENTRATE_MIX_RATIO = (0, DataTypes.F32) # Acid or Bicarb Concentrate mix ratio. + FULL_BOTTLE_VOLUME_ML = (1, DataTypes.F32) # Acid or Bicarb full bottle volume in milliliters. + CONDUCTIVITY_USPCM = (2, DataTypes.F32) # Acid or Bicarb conductivity in uS/cm. + BOTTLE_TEMPERATURE_C = (3, DataTypes.F32) # Acid or Bicarb bottle temperature in C. + CALIBRATION_TIME = (4, DataTypes.U32) # Calibration time + CRC = (5, DataTypes.U16) # CRC + NUM_OF_CALIBRATION_RECORD_CONCENTRATE_FIELDS = (6, DataTypes.NONE) # Number of Calibration Record Acid and Bicarb Concentrate's Fields + +CalibRecordConcentrateFields._str_list = { + # Official Name : Accepted strings + 'CONCENTRATE_MIX_RATIO': [], + 'FULL_BOTTLE_VOLUME_ML': [], + 'CONDUCTIVITY_USPCM': [], + 'BOTTLE_TEMPERATURE_C': [], + 'CALIBRATION_TIME': [], + 'CRC': [], + 'NUM_OF_CALIBRATION_RECORD_CONCENTRATE_FIELDS': [], +} + + +@unique +class CalibRecordSensorFields(RecordsBaseEnum): + FOURTH_ORDER_COEFF = (0, DataTypes.F32) # Sensor's Fourth order coefficient + THIRD_ORDER_COEFF = (1, DataTypes.F32) # Sensor's Third order coefficient + SECOND_ORDER_COEFF = (2, DataTypes.F32) # Sensor's Second order coefficient + GAIN = (3, DataTypes.F32) # Sensor's Gain + OFFSET = (4, DataTypes.F32) # Sensor's Offset + CALIBRATION_TIME = (5, DataTypes.U32) # Sensor's Calibration time + CRC = (6, DataTypes.U16) # Sensor's CRC + NUM_OF_CALIBRATION_RECORD_SENSOR_FIELDS = (7, DataTypes.NONE) # Number of Calibration Record Sensor's Fields + +CalibRecordSensorFields._str_list = { + # Official Name : Accepted strings + 'FORTH_ORDER_COEFF': [], + 'THIRD_ORDER_COEFF': [], + 'SECOND_ORDER_COEFF': [], + 'GAIN': [], + 'OFFSET': [], + 'CALIBRATION_TIME': [], + 'CRC': [], + 'NUM_OF_CALIBRATION_RECORD_SENSOR_FIELDS': [], +} + + +# ================================================== Enum Creators: Record Fields ================================================== +@unique +class RecordTypes(DialinEnum): + SYSTEM_RECORD = 0 # System Record + SERVICE_RECORD = 1 # System Record + CALIBRATION_RECORD = 2 # System Record + INSTITUTIONAL_RECORD = 3 # System Record + USAGE_INFORMATION_RECORD = 4 # System Record + NUM_OF_RECORD_TYPES = 5 # Number of Record Types + +RecordTypes._str_list = { + # Official Name : Accepted strings + 'SYSTEM_RECORD': [], + 'SERVICE_RECORD': [], + 'CALIBRATION_RECORD': [], + 'INSTITUTIONAL_RECORD': [], + 'USAGE_INFORMATION_RECORD': [], + 'NUM_OF_RECORD_TYPES': [], +} + + +@unique +class SystemRecordFields(RecordsBaseEnum): + IS_RO_FEATURED = (0, DataTypes.BOOL) # Is RO featured or defeatured + IS_RO_BOOST_PUMP_ADDED = (1, DataTypes.BOOL) # Is RO have boost pump or not + TOP_LEVEL_PN = (2, DataTypes.U08, 10) # Top level part number + TOP_LEVEL_SN = (3, DataTypes.U08, 20) # Top level serial number + MFG_LOCATION = (4, DataTypes.U08) # Manufacturing Location + MFG_DATE = (5, DataTypes.U32) # Manufacturing Date + CRC = (6, DataTypes.U16) # CRC + NUM_OF_SYSTEM_RECORD_FIELDS = (7, DataTypes.NONE) # Number of System Record Fields + +SystemRecordFields._str_list = { + # Official Name : Accepted strings + 'TOP_LEVEL_PN': ['pn', 'part number'], + 'TOP_LEVEL_SN': ['sn', 'serial number'], + 'MFG_LOCATION': ['location', 'manufacturing location'], + 'MFG_DATE': ['date', 'manufacturing date'], + 'CRC': [], + 'NUM_OF_SYSTEM_RECORD_FIELDS': [], +} + + +@unique +class ServiceRecordFields(RecordsBaseEnum): + IS_HDF_ONLINE_FLUID = (0, DataTypes.BOOL) # DD Is HDF Online + WATER_RECOVERY = (1, DataTypes.U32) # DD Water Recovery + SERVICE_LOC = (2, DataTypes.U08) # DD service location + LAST_SERVICE_EPOCH_DATE = (3, DataTypes.U32) # DD last service date in epoch + SERVICE_INTERVAL_SECONDS = (4, DataTypes.U32) # DD service interval in seconds + LAST_RESET_TIME_EPOCH = (5, DataTypes.U32) # Last time the record was reset in epoch + CRC = (6, DataTypes.U16) # CRC + NUM_OF_SERVICE_RECORD_FIELDS = (7, DataTypes.NONE) # Number of Service Record Fields + +ServiceRecordFields._str_list = { + # Official Name : Accepted strings + 'IS_HDF_ONLINE_FLUID': [], + 'WATER_RECOVERY': [], + 'SERVICE_LOC': [], + 'LAST_SERVICE_EPOCH_DATE': [], + 'SERVICE_INTERVAL_SECONDS': [], + 'LAST_RESET_TIME_EPOCH': [], + 'CRC': [], + 'NUM_OF_SERVICE_RECORD_FIELDS': [], +} + + +def get_calibration_record_fields() -> RecordsBaseEnum: + # Creating the enum members list + i = 0 + members = {} + for pres_sensor in DDPressureSensorNames: + if 'num_' in pres_sensor.name.lower(): + continue + for field_name in CalibRecordSensorFields: + if 'num_' in field_name.name.lower(): + continue + members[f'PRES__{pres_sensor.name}__{field_name.name}'] = (i, field_name.datatype()) + i += 1 + for pres_sensor in FPPressureSensorNames: + if 'num_' in pres_sensor.name.lower(): + continue + for field_name in CalibRecordSensorFields: + if 'num_' in field_name.name.lower(): + continue + members[f'PRES__{pres_sensor.name}__{field_name.name}'] = (i, field_name.datatype()) + i += 1 + for temp_sensor in DDTemperatureSensorNames: + if 'num_' in temp_sensor.name.lower(): + continue + elif temp_sensor.value >= DDTemperatureSensorNames.FIRST_CONDUCTIVITY_SENSOR.value: + continue + for field_name in CalibRecordSensorFields: + if 'num_' in field_name.name.lower(): + continue + members[f'TEMP__{temp_sensor.name}__{field_name.name}'] = (i, field_name.datatype()) + i += 1 + for conc_pump in DDConcentratePumpNames: + if 'num_' in conc_pump.name.lower(): + continue + for field_name in CalibRecordSensorFields: + if 'num_' in field_name.name.lower(): + continue + members[f'CONC__{conc_pump.name}__{field_name.name}'] = (i, field_name.datatype()) + i += 1 + for dial_pump in DDDialysatePumpNames: + if 'num_' in dial_pump.name.lower(): + continue + elif 'd12' in dial_pump.name.lower(): + members[f'DIAL__{dial_pump.name}__TARGET_SPEED'] = (i, DataTypes.F32) + i += 1 + members[f'DIAL__{dial_pump.name}__{CalibRecordSensorFields.CALIBRATION_TIME.name}'] = (i, CalibRecordSensorFields.CALIBRATION_TIME.datatype()) + i += 1 + members[f'DIAL__{dial_pump.name}__{CalibRecordSensorFields.CRC.name}'] = (i, CalibRecordSensorFields.CRC.datatype()) + i += 1 + else: + for field_name in CalibRecordSensorFields: + if 'num_' in field_name.name.lower(): + continue + members[f'DIAL__{dial_pump.name}__{field_name.name}'] = (i, field_name.datatype()) + i += 1 + for acid in AcidTypes: + if 'num_' in acid.name.lower(): + continue + for field_name in CalibRecordConcentrateFields: + if 'num_' in field_name.name.lower(): + continue + members[f'ACID__{acid.name}__{field_name.name}'] = (i, field_name.datatype()) + i += 1 + for bicarb in BicarbTypes: + if 'num_' in bicarb.name.lower(): + continue + for field_name in CalibRecordConcentrateFields: + if 'num_' in field_name.name.lower(): + continue + members[f'BICARB__{bicarb.name}__{field_name.name}'] = (i, field_name.datatype()) + i += 1 + for field_name in CalibRecordAccelerometerFields: + if 'num_' in field_name.name.lower(): + continue + members[f'ACCEL__{field_name.name}'] = (i, field_name.datatype()) + i += 1 + for field_name in CalibRecordBloodLeakFields: + if 'num_' in field_name.name.lower(): + continue + members[f'BLOOD_LEAK__{field_name.name}'] = (i, field_name.datatype()) + i += 1 + members[f'NUM_OF_CALIBRATION_RECORD_FIELDS'] = (i, DataTypes.NONE) + + # Enum creation + CalibrationRecordFields = RecordsBaseEnum('CalibrationRecordFields', members,) + + # Setting the _str_list values for the enum + CalibrationRecordFields._str_list = {} + for member in members: + CalibrationRecordFields._str_list[member] = [] + + return CalibrationRecordFields + + +@unique +class InstitutionalRecordFields(RecordsBaseEnum): + MIN_DIALYSATE_SYSTEM_FLOW_MLPM = (0, DataTypes.U32) # Min dialysate flow in mL/min + MAX_DIALYSATE_SYSTEM_FLOW_MLPM = (1, DataTypes.U32) # Max dialysate flow in mL/min + MIN_DIALYSATE_TEMP_C = (2, DataTypes.F32) # Min dialysate temperature in C + MAX_DIALYSATE_TEMP_C = (3, DataTypes.F32) # Max dialysate temperature in C + MIN_ACID_CONCENTRATE = (4, DataTypes.U32) # Min Acid concentrate + MAX_ACID_CONCENTRATE = (5, DataTypes.U32) # Max Acid concentrate + MIN_BICARB_CARTRIDGE_SIZE_G = (6, DataTypes.U32) # Min Bicarbonate cartridge size in grams + MAX_BICARB_CARTRIDGE_SIZE_G = (7, DataTypes.U32) # Max Bicarbonate cartridge size in grams + MIN_SODIUM_MEQPL = (8, DataTypes.U32) # Min sodium in mEq/L + MAX_SODIUM_MEQPL = (9, DataTypes.U32) # Max sodium in mEq/L + MIN_BICARBONATE_MEQPL = (10, DataTypes.U32) # Min bicarbonate in mEq/L + MAX_BICARBONATE_MEQPL = (11, DataTypes.U32) # Max bicarbonate in mEq/L + MIN_RO_REJECTION_RATIO_PCT = (12, DataTypes.U32) # Min RO rejection ratio in percent + DISINFECTION_FREQUENCY = (13, DataTypes.U32) # Disinfection days between cycles + DISINFECTION_CYCLE_TIME = (14, DataTypes.F32) # Disinfection Cycle Time + MIN_INLET_WATER_CIND_AKARN_LIMIT_USPCM = (15, DataTypes.F32) # Min inlet water conductivity alarm limit in uS/cm + MAX_INLET_WATER_CIND_AKARN_LIMIT_USPCM = (16, DataTypes.F32) # Max inlet water conductivity alarm limit in uS/cm + ACID_CONCENTRATE_JUG_SIZE_L = (17, DataTypes.F32) # acid concentrate jug size in Liters + MIN_ACID_ALARM_LIMIT_PCT = (18, DataTypes.F32) # Min acid alarm limit in percent + MIN_BICARB_ALARM_LIMIT_PCT = (19, DataTypes.F32) # Min bicarbonate alarm limit in percent + POST_TREAT_DRAIN_OPTION = (20, DataTypes.U32) # Dialysate Post Treatment Drain OptionĀ  + POST_TREAT_DRY_BICARB_OPTION = (21, DataTypes.U32) # Dry Bicarbonate Post Treatment OptionĀ  + CALIBRATION_TIME = (22, DataTypes.U32) # Calibration time in epoch. + CRC = (23, DataTypes.U16) # CRC + NUM_OF_INSTITUTIONAL_RECORD_FIELDS = (24, DataTypes.NONE) # Number of Institutional Record Fields + +InstitutionalRecordFields._str_list = { + # Official Name : Accepted strings + 'MIN_DIALYSATE_SYSTEM_FLOW_MLPM': [], + 'MAX_DIALYSATE_SYSTEM_FLOW_MLPM': [], + 'MIN_DIALYSATE_TEMP_C': [], + 'MAX_DIALYSATE_TEMP_C': [], + 'ACID_CONCENTRATE': [], + 'BICARB_CARTRIDGE_SIZE_G': [], + 'MIN_SODIUM_MEQPL': [], + 'MAX_SODIUM_MEQPL': [], + 'MIN_BICARBONATE_MEQPL': [], + 'MAX_BICARBONATE_MEQPL': [], + 'MIN_RO_REJECTION_RATIO_PCT': [], + 'DISINFECTION_FREQUENCY': [], + 'DISINFECTION_CYCLE_TIME': [], + 'MIN_INLET_WATER_CIND_AKARN_LIMIT_USPCM': [], + 'MAX_INLET_WATER_CIND_AKARN_LIMIT_USPCM': [], + 'ACID_CONCENTRATE_JUG_SIZE_L': [], + 'MIN_ACID_ALARM_LIMIT_PCT': [], + 'MIN_BICARB_ALARM_LIMIT_PCT': [], + 'POST_TREAT_DRAIN_OPTION': [], + 'POST_TREAT_DRY_BICARB_OPTION': [], + 'CALIBRATION_TIME': [], + 'CRC': [], + 'NUM_OF_INSTITUTIONAL_RECORD_FIELDS': [], +} + + +@unique +class UsageInformationRecordFields(RecordsBaseEnum): + RO_WATER_GEN_TOTAL_L = (0, DataTypes.F32) # Total RO water generated in liters. (Cannot be reset) + RO_WATER_GEN_SINCE_LAST_SERVICE_L = (1, DataTypes.F32) # RO water generated since last treatment in liters + LAST_BASIC_FLUSH_COMPLETE_DATE_EPOCH = (2, DataTypes.U32) # Last basic flush complete date in epoch. + LAST_HEAT_DISINFECT_COMPLETE_DATE_EPOCH = (3, DataTypes.U32) # Last heat disinfect complete date in epoch. + LAST_HEAT_ACTIVE_COOL_COMPLETE_DATE_EPOCH = (4, DataTypes.U32) # Last heat disinfect active cool complete date in epoch. + LAST_FILTER_FLUSH_COMPLETE_DATE_EPOCH = (5, DataTypes.U32) # Last filter flush complete date in epoch. + LAST_RESET_TIME_EPOCH = (6, DataTypes.U32) # Last time the record was reset in epoch. + CRC = (7, DataTypes.U16) # CRC + NUM_OF_USAGE_INFO_RECORD_FIELDS = (8, DataTypes.NONE) # Number of Usage Information Record Fields + +UsageInformationRecordFields._str_list = { + # Official Name : Accepted strings + 'RO_WATER_GEN_TOTAL_L': [], + 'RO_WATER_GEN_SINCE_LAST_SERVICE_L': [], + 'LAST_BASIC_FLUSH_COMPLETE_DATE_EPOCH': [], + 'LAST_HEAT_DISINFECT_COMPLETE_DATE_EPOCH': [], + 'LAST_HEAT_ACTIVE_COOL_COMPLETE_DATE_EPOCH': [], + 'LAST_FILTER_FLUSH_COMPLETE_DATE_EPOCH': [], + 'LAST_RESET_TIME_EPOCH': [], + 'CRC': [], + 'NUM_OF_USAGE_INFO_RECORD_FIELDS': [], +} Index: leahi_dialin/common/td_defs_proxy.py =================================================================== diff -u -re512c4de9e830fa4ac1ab5a3b06807d0d170d892 -r4c11f61e1e328407ed98a1b81a3e8b0adb278bd5 --- leahi_dialin/common/td_defs_proxy.py (.../td_defs_proxy.py) (revision e512c4de9e830fa4ac1ab5a3b06807d0d170d892) +++ leahi_dialin/common/td_defs_proxy.py (.../td_defs_proxy.py) (revision 4c11f61e1e328407ed98a1b81a3e8b0adb278bd5) @@ -102,4 +102,19 @@ AlarmProperties = generic_defs.AlarmProperties AlarmResponseButtons = generic_defs.AlarmResponseButtons + # Records + RecordTypes = generic_defs.RecordTypes + SystemRecordFields = generic_defs.SystemRecordFields + ServiceRecordFields = generic_defs.ServiceRecordFields + CalibrationRecordFields = generic_defs.get_calibration_record_fields() + InstitutionalRecordFields = generic_defs.InstitutionalRecordFields + UsageInformationRecordFields = generic_defs.UsageInformationRecordFields + + # Record support + CalibRecordAccelerometerFields = generic_defs.CalibRecordAccelerometerFields + CalibRecordBloodLeakFields = generic_defs.CalibRecordBloodLeakFields + CalibRecordConcentrateFields = generic_defs.CalibRecordConcentrateFields + CalibRecordSensorFields = generic_defs.CalibRecordSensorFields + + td_enum_repository = TD_Defs() Index: leahi_dialin/dd/dialysate_delivery.py =================================================================== diff -u -r5994adea1a1022175f9cc4e6dacfa02de3fc45a0 -r4c11f61e1e328407ed98a1b81a3e8b0adb278bd5 --- leahi_dialin/dd/dialysate_delivery.py (.../dialysate_delivery.py) (revision 5994adea1a1022175f9cc4e6dacfa02de3fc45a0) +++ leahi_dialin/dd/dialysate_delivery.py (.../dialysate_delivery.py) (revision 4c11f61e1e328407ed98a1b81a3e8b0adb278bd5) @@ -46,7 +46,7 @@ from ..common.constants import NO_RESET from ..common import dd_enum_repository from ..common.generic_defs import DataTypes -from ..common.msg_defs import MsgIds, MsgFieldPositions +from ..common.msg_ids import MsgIds from ..common.override_templates import cmd_generic_broadcast_interval_override, cmd_generic_override from ..protocols.CAN import CanMessage, CanMessenger, CanChannels from leahi_dialin.utils.abstract_classes import AbstractSubSystem Index: leahi_dialin/dd/modules/balancing_chamber.py =================================================================== diff -u -ra68abf5742f400a8d97f4cf9ee4ce64edd64531c -r4c11f61e1e328407ed98a1b81a3e8b0adb278bd5 --- leahi_dialin/dd/modules/balancing_chamber.py (.../balancing_chamber.py) (revision a68abf5742f400a8d97f4cf9ee4ce64edd64531c) +++ leahi_dialin/dd/modules/balancing_chamber.py (.../balancing_chamber.py) (revision 4c11f61e1e328407ed98a1b81a3e8b0adb278bd5) @@ -20,7 +20,7 @@ # Project imports from leahi_dialin.common.constants import NO_RESET from leahi_dialin.common.generic_defs import DataTypes -from leahi_dialin.common.msg_defs import MsgIds +from leahi_dialin.common.msg_ids import MsgIds from leahi_dialin.common.override_templates import cmd_generic_broadcast_interval_override, cmd_generic_override from leahi_dialin.protocols.CAN import CanMessenger, CanChannels from leahi_dialin.utils.abstract_classes import AbstractSubSystem Index: leahi_dialin/td/treatment_delivery.py =================================================================== diff -u -r01af2d84b2cb4aa87c58f10eb8af669820e71f49 -r4c11f61e1e328407ed98a1b81a3e8b0adb278bd5 --- leahi_dialin/td/treatment_delivery.py (.../treatment_delivery.py) (revision 01af2d84b2cb4aa87c58f10eb8af669820e71f49) +++ leahi_dialin/td/treatment_delivery.py (.../treatment_delivery.py) (revision 4c11f61e1e328407ed98a1b81a3e8b0adb278bd5) @@ -41,13 +41,13 @@ from .proxies.dd_proxy import DDProxy from .proxies.ui_proxy import UIProxy -from ..common.constants import NO_RESET -from ..common.msg_defs import MsgIds, MsgFieldPositions +from ..common.constants import NO_RESET, MSG_HEADER_SIZE +from ..common.msg_ids import MsgIds from ..common import td_enum_repository from ..common.generic_defs import DataTypes from ..common.override_templates import cmd_generic_broadcast_interval_override, cmd_generic_override from ..protocols.CAN import CanMessage, CanMessenger, CanChannels -from ..utils.abstract_classes import AbstractSubSystem +from ..utils.abstract_classes import AbstractSubSystem, LocalVars from ..utils.base import publish, LogManager from ..utils.conversions import integer_to_bytearray, bytearray_to_byte @@ -114,19 +114,16 @@ self.td_debug_events_timestamp = 0.0 #: The timestamp of the latest events message self.ui_version_info_response_timestamp = 0.0 #: The timestamp of the latest UI version info message self.td_version_response_timestamp = 0.0 #: The timestamp of the latest TD version info message - self.td_operation_mode = td_enum_repository.TDOpModes.MODE_INIT.value #: The Operation Mode's value - self.td_operation_sub_mode = 0 #: The Operation Sub-Mode's value - self.td_logged_in = False #: The value showing if the user is logged in or not - self.td_set_logged_in_status(False) - self.ui_version = None #: The UI's version value - self.td_version = None #: The TD's version value - self.td_fpga_version = None #: The TD's FPGA version value + self.td_operation_mode = td_enum_repository.TDOpModes.MODE_INIT.value + self.td_operation_sub_mode = 0 + self.ui_version = None + self.td_version = None + self.td_fpga_version = None self.td_debug_events = [''] * self._TD_DEBUG_EVENT_LIST_COUNT #: The Debug Event's list self.td_debug_event_index = 0 #: The index of the last Event self.td_last_debug_event = '' #: The name of the last Event # Create command groups - self.air_pump = TDAirPump(self.can_interface, self.logger) #: The Air Pump module self.air_trap = TDAirTrap(self.can_interface, self.logger) #: The Air Trap module self.alarms = TDAlarms(self.can_interface, self.logger) #: The Alarms module @@ -150,18 +147,82 @@ self.ui_proxy = UIProxy(self.can_interface, self.logger) #: The UI Proxy module (imitates commands sent by UI) + +# ============================================================ Properties ============================================================ + @property + def td_operation_mode(self) -> int: + """ + The current Operation Mode + """ + return self._op_mode + + @td_operation_mode.setter + def td_operation_mode(self, value): + self._op_mode = value + + + @property + def td_operation_sub_mode(self) -> int: + """ + The current Operation Sub Mode + """ + return self._sub_mode + + @td_operation_sub_mode.setter + def td_operation_sub_mode(self, value): + self._sub_mode = value + + + @property + def ui_version(self) -> int: + """ + The UI Version data + """ + return self._ui_version + + @ui_version.setter + def ui_version(self, value): + self._ui_version = value + + + @property + def td_version(self) -> int: + """ + The TD Version data + """ + return self._td_version + + @td_version.setter + def td_version(self, value): + self._td_version = value + + + @property + def td_fpga_version(self) -> int: + """ + The TD FPGA Version data + """ + return self._fpga_version + + @td_fpga_version.setter + def td_fpga_version(self, value): + self._fpga_version = value + + + +# ============================================================ Handlers ============================================================ @publish(["msg_id_td_debug_event", "td_debug_events_timestamp","td_debug_events"]) def _handler_td_debug_event_sync(self, message, timestamp = 0.0): payload = message['message'] message_length = payload[self._TD_DEBUG_EVENT_MSG_LEN_INDEX] temp_message = '' - index = MsgFieldPositions.START_POS_FIELD_1 + index = MSG_HEADER_SIZE for i in range(0, message_length): # Loop through the length and get the - char, char_index = bytearray_to_byte(payload, index + i, False) + char, index = bytearray_to_byte(payload, index, False) # The return value is the incremented index temp_message += chr(char) self.td_debug_events_timestamp = timestamp @@ -173,16 +234,7 @@ self.td_debug_event_index = 0 - def td_set_logged_in_status(self, logged_in: bool = False): - """ - Callback for td logged in status change. - @param logged_in: Logged in status for TD - @return: None - """ - self.td_logged_in = logged_in - - @publish(["msg_id_td_op_mode_data", "td_op_mode_timestamp","td_operation_mode", "td_operation_sub_mode"]) def _handler_td_op_mode_sync(self, message, timestamp = 0.0): """ @@ -193,11 +245,10 @@ @return: None """ msg_list = [] - msg_list.append(('self.td_operation_mode', DataTypes.U32)) - msg_list.append(('self.td_operation_sub_mode', DataTypes.U32)) + msg_list.append((type(self).td_operation_mode, DataTypes.U32)) + msg_list.append((type(self).td_operation_sub_mode, DataTypes.U32)) - self.process_into_vars(decoder_list = msg_list, - message = message) + self.process_into_vars(decoder_list = msg_list, message = message) self.td_op_mode_timestamp = timestamp @@ -210,26 +261,35 @@ @return: None if not successful, the version string if unpacked successfully """ + major = LocalVars('major') + minor = LocalVars('minor') + micro = LocalVars('micro') + build = LocalVars('build') + fpga_id = LocalVars('fpga_id') + fpga_major = LocalVars('fpga_major') + fpga_minor = LocalVars('fpga_minor') + fpga_lab = LocalVars('fpga_lab') + compatibility = LocalVars('compatibility') + msg_list = [] - msg_list.append(('major', DataTypes.U08)) - msg_list.append(('minor', DataTypes.U08)) - msg_list.append(('micro', DataTypes.U08)) - msg_list.append(('build', DataTypes.U16)) - msg_list.append(('fpga_id', DataTypes.U08)) - msg_list.append(('fpga_major', DataTypes.U08)) - msg_list.append(('fpga_minor', DataTypes.U08)) - msg_list.append(('fpga_lab', DataTypes.U08)) - msg_list.append(('compatibility', DataTypes.U32)) + msg_list.append((major, DataTypes.U08)) + msg_list.append((minor, DataTypes.U08)) + msg_list.append((micro, DataTypes.U08)) + msg_list.append((build, DataTypes.U16)) + msg_list.append((fpga_id, DataTypes.U08)) + msg_list.append((fpga_major, DataTypes.U08)) + msg_list.append((fpga_minor, DataTypes.U08)) + msg_list.append((fpga_lab, DataTypes.U08)) + msg_list.append((compatibility, DataTypes.U32)) - result = self.process_into_vars(decoder_list = msg_list, - message = message) + self.process_into_vars(decoder_list = msg_list, message = message) - if all([each is not None for each in [result['major'], result['minor'], result['micro'], result['build'], result['compatibility']]]): - self.td_version = f"v{result['major']}.{result['minor']}.{result['micro']}-{result['build']}.{result['compatibility']}" + if all([each is not None for each in [major.value, minor.value, micro.value, build.value, compatibility.value]]): + self.td_version = f"v{major.value}.{minor.value}.{micro.value}-{build.value}.{compatibility.value}" self.logger.debug(f'TD VERSION: {self.td_version}') - if all([each is not None for each in [result['fpga_id'], result['fpga_major'], result['fpga_minor'], result['fpga_lab']]]): - self.td_fpga_version = f"v{result['fpga_id']}.{result['fpga_major']}.{result['fpga_minor']}-{result['fpga_lab']}" + if all([each is not None for each in [fpga_id.value, fpga_major.value, fpga_minor.value, fpga_lab.value]]): + self.td_fpga_version = f"v{fpga_id.value}.{fpga_major.value}.{fpga_minor.value}-{fpga_lab.value}" self.logger.debug(f'TD FPGA VERSION: {self.td_fpga_version}') self.td_version_response_timestamp = timestamp @@ -249,24 +309,44 @@ @return: None if not successful, the version string if unpacked successfully """ + major = LocalVars('major') + minor = LocalVars('minor') + micro = LocalVars('micro') + build = LocalVars('build') + compatibility = LocalVars('compatibility') + msg_list = [] - msg_list.append(('major', DataTypes.U08)) - msg_list.append(('minor', DataTypes.U08)) - msg_list.append(('micro', DataTypes.U08)) - msg_list.append(('build', DataTypes.U16)) - msg_list.append(('compatibility', DataTypes.U32)) + msg_list.append((major, DataTypes.U08)) + msg_list.append((minor, DataTypes.U08)) + msg_list.append((micro, DataTypes.U08)) + msg_list.append((build, DataTypes.U16)) + msg_list.append((compatibility, DataTypes.U32)) - result = self.process_into_vars(decoder_list = msg_list, - message = message) + self.process_into_vars(decoder_list = msg_list, message = message) - if all([each is not None for each in [result['major'], result['minor'], result['micro'], result['build'], result['compatibility']]]): - self.ui_version = f"v{result['major']}.{result['minor']}.{result['micro']}-{result['build']}.{result['compatibility']}" + if all([each is not None for each in [major.value, minor.value, micro.value, build.value, compatibility.value]]): + self.ui_version = f"v{major.value}.{minor.value}.{micro.value}-{build.value}.{compatibility.value}" self.logger.debug(f'UI VERSION: {self.ui_version}') else: self.ui_version = None self.logger.debug("Failed to retrieve UI Version.") self.ui_version_info_response_timestamp = timestamp + + +# ============================================================ Support ============================================================ + def td_set_logged_in_status(self, logged_in: bool = False): + """ + Callback for td logged in status change. + + @param logged_in: Logged in status for TD + @return: None + """ + self.td_logged_in = logged_in + + + +# ============================================================ Overrides and Requests ============================================================ def cmd_op_mode_broadcast_interval_override(self, ms: int, reset: int = NO_RESET) -> int: """ Constructs and sends the measured op mode broadcast interval override command Index: leahi_dialin/utils/abstract_classes.py =================================================================== diff -u -rc41ad46a9dea345a8feb4e674b6753999e381629 -r4c11f61e1e328407ed98a1b81a3e8b0adb278bd5 --- leahi_dialin/utils/abstract_classes.py (.../abstract_classes.py) (revision c41ad46a9dea345a8feb4e674b6753999e381629) +++ leahi_dialin/utils/abstract_classes.py (.../abstract_classes.py) (revision 4c11f61e1e328407ed98a1b81a3e8b0adb278bd5) @@ -24,6 +24,16 @@ from leahi_dialin.common.constants import MSG_HEADER_SIZE +class LocalVars: + """ + Reference class for local variables + """ + + def __init__(self, name): + self.name = name + self.value = None + + class AbstractObserver(ABC): """ Publicly accessible parent class for all observers. @@ -66,7 +76,7 @@ self._observers.remove(observer) - def process_into_vars(self, decoder_list: List[Tuple], message, start_from_byte: int=0, debug: bool=False) -> dict: + def process_into_vars(self, decoder_list: List[Tuple], message, start_from_byte: int=0, debug: bool=False) -> None: """ Process the CAN message with the help of the decoder list into variables and a dictionary. Note: updating variables will only be done when it's class wide one, aka "self.attr_name". @@ -79,102 +89,49 @@ :return: (Dictionary) A dictionary for the variable_name and value pair """ start_pos = MSG_HEADER_SIZE + start_from_byte - results = {} + result = {} # Needed if datatype is a string and indicates an earlier entry's value. Entry will be stored here and then it's value is read from here if debug: - print(f'\n\ndecoder_list: {decoder_list}') - print(f'full message: {message["message"]}') + print(f'\n\nDecoder_list: {decoder_list}') for decode_details in decoder_list: - # Content of the decode list - variable_name = decode_details[0] - # If last position is multichar length if isinstance(decode_details[-1], int): length = decode_details[-1] datatype: DataTypes = decode_details[-2] - else: + base_list_length = 3 + # If it's the normal Datatype + elif isinstance(decode_details[-1], DataTypes): length = 1 datatype: DataTypes = decode_details[-1] + base_list_length = 2 + # If it's a string containing the name of the datatype + else: + length = 1 + datatype = DataTypes(result[decode_details[-1]]) + base_list_length = 2 + # Check if the decoder list contains keys indicating the target is a dictionary + key_list_length = len(decode_details) - base_list_length + key_1 = decode_details[1] if len(decode_details) >= base_list_length + 1 else None + key_2 = decode_details[2] if len(decode_details) >= base_list_length + 2 else None + key_3 = decode_details[3] if len(decode_details) >= base_list_length + 3 else None end_pos = start_pos + datatype.size() - - for i in range(0, length): - end_pos = start_pos + datatype.size() - try: - new_value = struct.unpack(datatype.unpack_attrib(), bytearray(message['message'][start_pos:end_pos]))[0] - if length == 1: - value = new_value - elif datatype == DataTypes.U08 and length > 1: - if i == 0: - value = chr(new_value) - else: - value += chr(new_value) - if debug: - print(f'Message Part: {message["message"][start_pos:end_pos]}') - print(f'Input Data: {bytearray(message["message"][start_pos:end_pos])}') - print(f'New value: {new_value} + -> Value: {value}') - except Exception as e: - value = None - break - start_pos = end_pos - - if 'nan' in str(value).lower(): - value = None - break - # raise ValueError(f'{value} is not an accepted value!') - if datatype in [DataTypes.BOOL, DataTypes.BOOL_U08]: - value = True if value == 1 else False - results[variable_name] = value - # If it's a instance variable (self.) then set it's value - if variable_name.startswith('self'): - attr_path = variable_name[5:].split('.') - obj = self - for attr in attr_path[:-1]: - obj = getattr(obj, attr) - setattr(obj, attr_path[-1], value) - start_pos = end_pos - if debug: - print('Finished cycle\n') - if debug: - print(f'results: {results}\n') - return results - - - def process_into_dict(self, dict_to_update: dict, decoder_list: List[Tuple], message, start_from_byte: int=0, debug: bool=False): - """ - Process the CAN message with the help of the decoder list into a dictionary. - - :param decoder_list: (List[Tuple[DialEnum, DialEnum, DataTypes]]) Contains the dictioarny key names and DataType of the indexed message - :param message: (Bytearray) The raw CAN message - :param start_from_byte: (Integer) Start from the nth byte after the header - :param debug: (Boolean) Prints for debugging - :return: (Dictionary) The updated dictionary - """ - start_pos = MSG_HEADER_SIZE + start_from_byte - if debug: - print(f'\n\ndecoder_list: {decoder_list}') - print(f'full message: {message["message"]}') - for decode_details in decoder_list: - # Content of the decode list - key_1 = decode_details[0] - key_2 = decode_details[1] if len(decode_details) >= 3 else None - key_3 = decode_details[2] if len(decode_details) >= 4 else None + print(f'Len: {len(decode_details)}') + print(f'key_list_length: {key_list_length}') + if key_list_length > 0: + print(f'key_1: {key_1}') + print(f'key_2: {key_2}') + print(f'key_3: {key_3}') + print(f'datatype: {datatype} - {length}') - # If last position is multichar length - if isinstance(decode_details[-1], int): - length = decode_details[-1] - datatype: DataTypes = decode_details[-2] - key_list_length = len(decode_details) - 2 - else: - length = 1 - datatype: DataTypes = decode_details[-1] - key_list_length = len(decode_details) - 1 - + # Extract the value and convert it to correct type for i in range(0, length): end_pos = start_pos + datatype.size() try: new_value = struct.unpack(datatype.unpack_attrib(), bytearray(message['message'][start_pos:end_pos]))[0] + # If not multilength just save it if length == 1: value = new_value + # If multilength entry (length > 1), convert it to character and add append it to the value until fully read elif datatype == DataTypes.U08 and length > 1: if i == 0: value = chr(new_value) @@ -185,6 +142,8 @@ print(f'Input Data: {bytearray(message["message"][start_pos:end_pos])}') print(f'New value: {new_value} + -> Value: {value}') except Exception as e: + if debug: + print('Message extraction exception occured!') value = None break start_pos = end_pos @@ -195,25 +154,49 @@ # If the type is Bool, convert the value from Integer to Boolean if datatype in [DataTypes.BOOL, DataTypes.BOOL_U08]: value = True if value == 1 else False - # Save the value into the Dictionary - if key_list_length == 1: - dict_to_update[key_1] = value - elif key_list_length == 2: - if key_1 not in dict_to_update: - dict_to_update[key_1] = {} - dict_to_update[key_1][key_2] = value - elif key_list_length == 3: - if key_1 not in dict_to_update: - dict_to_update[key_1] = {} - if key_2 not in dict_to_update[key_1]: - dict_to_update[key_1][key_2] = {} - dict_to_update[key_1][key_2][key_3] = value + # Save processed value into the target + # If the target is a property + if isinstance(decode_details[0], property): + property_name = decode_details[0].fget.__name__ + if debug: + print(f'Property name: {property_name} <- {value}') + decode_details[0].__set__(self, value) + result[property_name] = value + # If the target is a dictionary use the extracted keys to add it under the correct position + elif isinstance(decode_details[0], dict): + dict_to_update = decode_details[0] + if key_list_length == 1: + dict_to_update[key_1] = value + if debug: + print(f'Dictionary key [{key_1}] <- {value}') + elif key_list_length == 2: + if key_1 not in dict_to_update: + dict_to_update[key_1] = {} + dict_to_update[key_1][key_2] = value + if debug: + print(f'Dictionary key [{key_1}][{key_2}] <- {value}') + elif key_list_length == 3: + if key_1 not in dict_to_update: + dict_to_update[key_1] = {} + if key_2 not in dict_to_update[key_1]: + dict_to_update[key_1][key_2] = {} + dict_to_update[key_1][key_2][key_3] = value + if debug: + print(f'Dictionary key [{key_1}][{key_2}][{key_3}] <- {value}') + # If the target is a list, append the current value to the end + elif isinstance(decode_details[0], list): + decode_details[0].append(value) + # If the target is a local variables + else: + variable_name = decode_details[0].name + if debug: + print(f'Local variable name: {variable_name} <- {value}') + decode_details[0].value = value + result[variable_name] = value start_pos = end_pos if debug: print('Finished cycle\n') - if debug: - print('done\n') def crc16(self, data: bytes) -> int: