Index: docs/source/conf.py =================================================================== diff -u -r07db7b5f01ad17d60c190e21574e1ed0552535ff -r2138d06d100fdcf23f2e9069f35ee2fdee62008f --- docs/source/conf.py (.../conf.py) (revision 07db7b5f01ad17d60c190e21574e1ed0552535ff) +++ docs/source/conf.py (.../conf.py) (revision 2138d06d100fdcf23f2e9069f35ee2fdee62008f) @@ -155,7 +155,7 @@ # One entry per manual page. List of tuples # (source start file, name, description, authors, manual section). man_pages = [ - (master_doc, 'leahi-dialin', 'Dialin Documentation', + (master_doc, 'leahi_dialin', 'Dialin Documentation', [author], 1) ] Fisheye: Tag 2138d06d100fdcf23f2e9069f35ee2fdee62008f refers to a dead (removed) revision in file `leahi-dialin/__init__.py'. Fisheye: No comparison available. Pass `N' to diff? Fisheye: Tag 2138d06d100fdcf23f2e9069f35ee2fdee62008f refers to a dead (removed) revision in file `leahi-dialin/common/__init__.py'. Fisheye: No comparison available. Pass `N' to diff? Fisheye: Tag 2138d06d100fdcf23f2e9069f35ee2fdee62008f refers to a dead (removed) revision in file `leahi-dialin/common/alarm_defs.py'. Fisheye: No comparison available. Pass `N' to diff? Fisheye: Tag 2138d06d100fdcf23f2e9069f35ee2fdee62008f refers to a dead (removed) revision in file `leahi-dialin/common/alarm_priorities.py'. Fisheye: No comparison available. Pass `N' to diff? Fisheye: Tag 2138d06d100fdcf23f2e9069f35ee2fdee62008f refers to a dead (removed) revision in file `leahi-dialin/common/dd_defs.py'. Fisheye: No comparison available. Pass `N' to diff? Fisheye: Tag 2138d06d100fdcf23f2e9069f35ee2fdee62008f refers to a dead (removed) revision in file `leahi-dialin/common/msg_defs.py'. Fisheye: No comparison available. Pass `N' to diff? Fisheye: Tag 2138d06d100fdcf23f2e9069f35ee2fdee62008f refers to a dead (removed) revision in file `leahi-dialin/common/msg_ids.py'. Fisheye: No comparison available. Pass `N' to diff? Fisheye: Tag 2138d06d100fdcf23f2e9069f35ee2fdee62008f refers to a dead (removed) revision in file `leahi-dialin/common/td_defs.py'. Fisheye: No comparison available. Pass `N' to diff? Fisheye: Tag 2138d06d100fdcf23f2e9069f35ee2fdee62008f refers to a dead (removed) revision in file `leahi-dialin/common/test_config_defs.py'. Fisheye: No comparison available. Pass `N' to diff? Fisheye: Tag 2138d06d100fdcf23f2e9069f35ee2fdee62008f refers to a dead (removed) revision in file `leahi-dialin/common/ui_defs.py'. Fisheye: No comparison available. Pass `N' to diff? Fisheye: Tag 2138d06d100fdcf23f2e9069f35ee2fdee62008f refers to a dead (removed) revision in file `leahi-dialin/dd/__init__.py'. Fisheye: No comparison available. Pass `N' to diff? Fisheye: Tag 2138d06d100fdcf23f2e9069f35ee2fdee62008f refers to a dead (removed) revision in file `leahi-dialin/dd/conductivity_sensors.py'. Fisheye: No comparison available. Pass `N' to diff? Fisheye: Tag 2138d06d100fdcf23f2e9069f35ee2fdee62008f refers to a dead (removed) revision in file `leahi-dialin/dd/dialysate_delivery.py'. Fisheye: No comparison available. Pass `N' to diff? Fisheye: Tag 2138d06d100fdcf23f2e9069f35ee2fdee62008f refers to a dead (removed) revision in file `leahi-dialin/dd/pressure_sensors.py'. Fisheye: No comparison available. Pass `N' to diff? Fisheye: Tag 2138d06d100fdcf23f2e9069f35ee2fdee62008f refers to a dead (removed) revision in file `leahi-dialin/dd/td_proxy.py'. Fisheye: No comparison available. Pass `N' to diff? Fisheye: Tag 2138d06d100fdcf23f2e9069f35ee2fdee62008f refers to a dead (removed) revision in file `leahi-dialin/dd/valves.py'. Fisheye: No comparison available. Pass `N' to diff? Fisheye: Tag 2138d06d100fdcf23f2e9069f35ee2fdee62008f refers to a dead (removed) revision in file `leahi-dialin/protocols/CAN.py'. Fisheye: No comparison available. Pass `N' to diff? Fisheye: Tag 2138d06d100fdcf23f2e9069f35ee2fdee62008f refers to a dead (removed) revision in file `leahi-dialin/protocols/__init__.py'. Fisheye: No comparison available. Pass `N' to diff? Fisheye: Tag 2138d06d100fdcf23f2e9069f35ee2fdee62008f refers to a dead (removed) revision in file `leahi-dialin/ro/__init__.py'. Fisheye: No comparison available. Pass `N' to diff? Fisheye: Tag 2138d06d100fdcf23f2e9069f35ee2fdee62008f refers to a dead (removed) revision in file `leahi-dialin/ro/reverse_osmosis.py'. Fisheye: No comparison available. Pass `N' to diff? Fisheye: Tag 2138d06d100fdcf23f2e9069f35ee2fdee62008f refers to a dead (removed) revision in file `leahi-dialin/td/__init__.py'. Fisheye: No comparison available. Pass `N' to diff? Fisheye: Tag 2138d06d100fdcf23f2e9069f35ee2fdee62008f refers to a dead (removed) revision in file `leahi-dialin/td/modules/__init__.py'. Fisheye: No comparison available. Pass `N' to diff? Fisheye: Tag 2138d06d100fdcf23f2e9069f35ee2fdee62008f refers to a dead (removed) revision in file `leahi-dialin/td/modules/air_pump.py'. Fisheye: No comparison available. Pass `N' to diff? Fisheye: Tag 2138d06d100fdcf23f2e9069f35ee2fdee62008f refers to a dead (removed) revision in file `leahi-dialin/td/modules/air_trap.py'. Fisheye: No comparison available. Pass `N' to diff? Fisheye: Tag 2138d06d100fdcf23f2e9069f35ee2fdee62008f refers to a dead (removed) revision in file `leahi-dialin/td/modules/alarms.py'. Fisheye: No comparison available. Pass `N' to diff? Fisheye: Tag 2138d06d100fdcf23f2e9069f35ee2fdee62008f refers to a dead (removed) revision in file `leahi-dialin/td/modules/blood_flow.py'. Fisheye: No comparison available. Pass `N' to diff? Fisheye: Tag 2138d06d100fdcf23f2e9069f35ee2fdee62008f refers to a dead (removed) revision in file `leahi-dialin/td/modules/bubble_detector.py'. Fisheye: No comparison available. Pass `N' to diff? Fisheye: Tag 2138d06d100fdcf23f2e9069f35ee2fdee62008f refers to a dead (removed) revision in file `leahi-dialin/td/modules/buttons.py'. Fisheye: No comparison available. Pass `N' to diff? Fisheye: Tag 2138d06d100fdcf23f2e9069f35ee2fdee62008f refers to a dead (removed) revision in file `leahi-dialin/td/modules/pressure_sensors.py'. Fisheye: No comparison available. Pass `N' to diff? Fisheye: Tag 2138d06d100fdcf23f2e9069f35ee2fdee62008f refers to a dead (removed) revision in file `leahi-dialin/td/modules/switches.py'. Fisheye: No comparison available. Pass `N' to diff? Fisheye: Tag 2138d06d100fdcf23f2e9069f35ee2fdee62008f refers to a dead (removed) revision in file `leahi-dialin/td/modules/valves.py'. Fisheye: No comparison available. Pass `N' to diff? Fisheye: Tag 2138d06d100fdcf23f2e9069f35ee2fdee62008f refers to a dead (removed) revision in file `leahi-dialin/td/modules/voltages.py'. Fisheye: No comparison available. Pass `N' to diff? Fisheye: Tag 2138d06d100fdcf23f2e9069f35ee2fdee62008f refers to a dead (removed) revision in file `leahi-dialin/td/proxies/__init__.py'. Fisheye: No comparison available. Pass `N' to diff? Fisheye: Tag 2138d06d100fdcf23f2e9069f35ee2fdee62008f refers to a dead (removed) revision in file `leahi-dialin/td/proxies/dd_proxy.py'. Fisheye: No comparison available. Pass `N' to diff? Fisheye: Tag 2138d06d100fdcf23f2e9069f35ee2fdee62008f refers to a dead (removed) revision in file `leahi-dialin/td/proxies/ro_proxy.py'. Fisheye: No comparison available. Pass `N' to diff? Fisheye: Tag 2138d06d100fdcf23f2e9069f35ee2fdee62008f refers to a dead (removed) revision in file `leahi-dialin/td/treatment_delivery.py'. Fisheye: No comparison available. Pass `N' to diff? Fisheye: Tag 2138d06d100fdcf23f2e9069f35ee2fdee62008f refers to a dead (removed) revision in file `leahi-dialin/ui/__init__.py'. Fisheye: No comparison available. Pass `N' to diff? Fisheye: Tag 2138d06d100fdcf23f2e9069f35ee2fdee62008f refers to a dead (removed) revision in file `leahi-dialin/utils/__init__.py'. Fisheye: No comparison available. Pass `N' to diff? Fisheye: Tag 2138d06d100fdcf23f2e9069f35ee2fdee62008f refers to a dead (removed) revision in file `leahi-dialin/utils/base.py'. Fisheye: No comparison available. Pass `N' to diff? Fisheye: Tag 2138d06d100fdcf23f2e9069f35ee2fdee62008f refers to a dead (removed) revision in file `leahi-dialin/utils/checks.py'. Fisheye: No comparison available. Pass `N' to diff? Fisheye: Tag 2138d06d100fdcf23f2e9069f35ee2fdee62008f refers to a dead (removed) revision in file `leahi-dialin/utils/conversions.py'. Fisheye: No comparison available. Pass `N' to diff? Fisheye: Tag 2138d06d100fdcf23f2e9069f35ee2fdee62008f refers to a dead (removed) revision in file `leahi-dialin/utils/data_logger.py'. Fisheye: No comparison available. Pass `N' to diff? Fisheye: Tag 2138d06d100fdcf23f2e9069f35ee2fdee62008f refers to a dead (removed) revision in file `leahi-dialin/utils/excel_ops.py'. Fisheye: No comparison available. Pass `N' to diff? Fisheye: Tag 2138d06d100fdcf23f2e9069f35ee2fdee62008f refers to a dead (removed) revision in file `leahi-dialin/utils/helpers.py'. Fisheye: No comparison available. Pass `N' to diff? Fisheye: Tag 2138d06d100fdcf23f2e9069f35ee2fdee62008f refers to a dead (removed) revision in file `leahi-dialin/utils/nv_ops_utils.py'. Fisheye: No comparison available. Pass `N' to diff? Fisheye: Tag 2138d06d100fdcf23f2e9069f35ee2fdee62008f refers to a dead (removed) revision in file `leahi-dialin/utils/singleton.py'. Fisheye: No comparison available. Pass `N' to diff? Fisheye: Tag 2138d06d100fdcf23f2e9069f35ee2fdee62008f refers to a dead (removed) revision in file `leahi-dialin/version.py'. Fisheye: No comparison available. Pass `N' to diff? Index: leahi_dialin/__init__.py =================================================================== diff -u --- leahi_dialin/__init__.py (revision 0) +++ leahi_dialin/__init__.py (revision 2138d06d100fdcf23f2e9069f35ee2fdee62008f) @@ -0,0 +1,19 @@ +from .td import * +from .dd import * +from .ro import * +from .ui import * +from .utils import * + +__version__ = "" + +try: + from importlib import metadata +except ImportError: # for Python < 3.8 + import importlib_metadata as metadata + +try: + __version__ = metadata.version(__name__) +except metadata.PackageNotFoundError: + # package is not installed + pass + Index: leahi_dialin/common/__init__.py =================================================================== diff -u --- leahi_dialin/common/__init__.py (revision 0) +++ leahi_dialin/common/__init__.py (revision 2138d06d100fdcf23f2e9069f35ee2fdee62008f) @@ -0,0 +1,6 @@ +from .alarm_defs import * +from .alarm_priorities import * +from .msg_defs import * +from .td_defs import * +from .dd_defs import * +from .ui_defs import * Index: leahi_dialin/common/alarm_defs.py =================================================================== diff -u --- leahi_dialin/common/alarm_defs.py (revision 0) +++ leahi_dialin/common/alarm_defs.py (revision 2138d06d100fdcf23f2e9069f35ee2fdee62008f) @@ -0,0 +1,111 @@ +########################################################################### +# +# 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 alarm_defs.py +# +# @author (last) Vinayakam Mani +# @date (last) 10-May-2024 +# @author (original) Peter Lucia +# @date (original) 07-Aug-2020 +# +############################################################################ +from enum import unique +from ..utils.base import AlarmEnum + + +# Branch: staging +@unique +class AlarmList(AlarmEnum): + ALARM_ID_NO_ALARM = 0 + ALARM_ID_TD_SOFTWARE_FAULT = 1 + ALARM_ID_DD_SOFTWARE_FAULT = 2 + ALARM_ID_RO_SOFTWARE_FAULT = 3 + ALARM_ID_TD_FPGA_POST_TEST_FAILED = 4 + ALARM_ID_TD_WATCHDOG_POST_TEST_FAILED = 5 + ALARM_ID_TD_UI_COMM_POST_FAILED = 6 + ALARM_ID_TD_FPGA_CLOCK_SPEED_CHECK_FAILURE = 7 + ALARM_ID_TD_FPGA_COMM_TIMEOUT = 8 + ALARM_ID_TD_UI_COMM_TIMEOUT = 9 + ALARM_ID_TD_DD_COMM_TIMEOUT = 10 + ALARM_ID_TD_COMM_TOO_MANY_BAD_CRCS = 11 + ALARM_ID_TD_CAN_MESSAGE_NOT_ACKED_BY_UI = 12 + ALARM_ID_TD_CAN_MESSAGE_NOT_ACKED_BY_DD = 13 + ALARM_ID_TD_CRITICAL_DATA_ERROR = 14 + ALARM_ID_DD_CRITICAL_DATA_ERROR = 15 + ALARM_ID_TD_WATCHDOG_EXPIRED = 16 + ALARM_ID_DD_WATCHDOG_EXPIRED = 17 + ALARM_ID_DD_COMM_TOO_MANY_BAD_CRCS = 18 + ALARM_ID_DD_CAN_MESSAGE_NOT_ACKED_BY_RO = 19 + ALARM_ID_DD_CAN_MESSAGE_NOT_ACKED_BY_TD = 20 + ALARM_ID_DD_FPGA_POST_TEST_FAILED = 21 + ALARM_ID_DD_FPGA_CLOCK_SPEED_CHECK_FAILURE = 22 + ALARM_ID_DD_FPGA_COMM_TIMEOUT = 23 + ALARM_ID_TD_ALARM_AUDIO_SELF_TEST_FAILURE = 24 + ALARM_ID_DD_VALVE_CONTROL_FAILURE = 25 + ALARM_ID_TD_ARTERIAL_SENSOR_TIMEOUT_FAULT = 26 + ALARM_ID_TD_VENOUS_SENSOR_TIMEOUT_FAULT = 27 + ALARM_ID_DD_HYD_OUTLET_PRES_TIMEOUT_FAULT = 28 + ALARM_ID_DD_BIBAG_PRES_TIMEOUT_FAULT = 29 + ALARM_ID_DD_SPENT_DIALYSATE_PRES_TIMEOUT_FAULT = 30 + ALARM_ID_DD_FRESH_DIALYSATE_PRES_TIMEOUT_FAULT = 31 + ALARM_ID_DD_TRANSMEMB_PRES_TIMEOUT_FAULT = 32 + ALARM_ID_TD_VOLTAGE_OUT_OF_RANGE = 33 + ALARM_ID_TD_AC_POWER_LOST = 34 + ALARM_ID_TD_AC_POWER_LOST_IN_TREATMENT = 35 + ALARM_ID_DD_CD1_SENSOR_FPGA_FAULT = 36 + ALARM_ID_DD_CD2_SENSOR_FPGA_FAULT = 37 + ALARM_ID_DD_CD3_SENSOR_FPGA_FAULT = 38 + ALARM_ID_DD_CD4_SENSOR_FPGA_FAULT = 39 + ALARM_ID_TD_VENOUS_BUBBLE_DETECTED = 40 + ALARM_ID_TD_CARTRIDGE_DOOR_OPENED = 41 + ALARM_ID_TD_STUCK_BUTTON_TEST_FAILED = 42 + ALARM_ID_TD_ARTERIAL_PRESSURE_LOW = 43 + ALARM_ID_TD_ARTERIAL_PRESSURE_HIGH = 44 + ALARM_ID_TD_ARTERIAL_PRESSURE_OUT_OF_RANGE = 45 + ALARM_ID_TD_ARTERIAL_PRESSURE_SELF_TEST_FAILURE = 46 + ALARM_ID_TD_VENOUS_PRESSURE_LOW = 47 + ALARM_ID_TD_VENOUS_PRESSURE_HIGH = 48 + ALARM_ID_TD_VENOUS_PRESSURE_OUT_OF_RANGE = 49 + ALARM_ID_TD_VENOUS_PRESSURE_SELF_TEST_FAILURE = 50 + ALARM_ID_TD_PRE_TREATMENT_DRY_PRESSURE_TEST_FAILURE = 51 + ALARM_ID_DD_CP1_SPEED_CONTROL_ERROR = 52 + ALARM_ID_DD_CP2_SPEED_CONTROL_ERROR = 53 + ALARM_ID_DD_CONC_PUMP_HALL_SENSOR_OUT_OF_RANGE = 54 + ALARM_ID_DD_CONCENTRATE_PUMP_FAULT = 55 + ALARM_ID_DD_TEMPERATURE_SENSOR_OUT_OF_RANGE = 56 + ALARM_ID_DD_RTD_SENSORS_FPGA_FAULT = 57 + ALARM_ID_DD_BARO_SENSOR_FPGA_FAULT = 58 + ALARM_ID_DD_BAROMETRIC_SENSOR_COEFFS_BAD_CRC = 59 + ALARM_ID_DD_FRESH_DIALYSATE_PUMP_RPM_OUT_OF_RANGE = 60 + ALARM_ID_DD_FRESH_DIALYSATE_PUMP_OFF_FAULT = 61 + ALARM_ID_DD_FRESH_DIALYSATE_PUMP_CURRENT_OUT_OF_RANGE = 62 + ALARM_ID_DD_FRESH_DIALYSATE_PUMP_DIRECTION_INVALID = 63 + ALARM_ID_DD_FRESH_DIALYSATE_PUMP_DIRECTION_FPGA_FAULT = 64 + ALARM_ID_DD_SPENT_DIALYSATE_PUMP_RPM_OUT_OF_RANGE = 65 + ALARM_ID_DD_SPENT_DIALYSATE_PUMP_OFF_FAULT = 66 + ALARM_ID_DD_SPENT_DIALYSATE_PUMP_CURRENT_OUT_OF_RANGE = 67 + ALARM_ID_DD_SPENT_DIALYSATE_PUMP_DIRECTION_INVALID = 68 + ALARM_ID_DD_SPENT_DIALYSATE_PUMP_DIRECTION_FPGA_FAULT = 69 + ALARM_ID_DD_FLUID_TOO_LOW_WHILE_PRIMARY_HEATER_IS_ON = 70 + ALARM_ID_DD_FLUID_TOO_LOW_WHILE_TRIMMER_HEATER_IS_ON = 71 + ALARM_ID_DD_WATER_INLET_INPUT_PRES_TIMEOUT_FAULT = 72 + ALARM_ID_DD_WATER_INLET_OUTPUT_PRES_TIMEOUT_FAULT = 73 + ALARM_ID_DD_INLET_WATER_PRESSURE_IN_LOW_RANGE = 74 + ALARM_ID_DD_INLET_WATER_PRESSURE_IN_HIGH_RANGE = 75 + ALARM_ID_DD_INLET_WATER_PRESSURE_OUT_LOW_RANGE = 76 + ALARM_ID_DD_INLET_WATER_PRESSURE_OUT_HIGH_RANGE = 77 + ALARM_ID_TD_AIR_TRAP_FILL_DURING_TREATMENT = 78 + ALARM_ID_TD_AIR_TRAP_ILLEGAL_LEVELS = 79 + ALARM_ID_TD_PINCH_VALVE_FAULT = 80 + ALARM_ID_TD_VALVE_HOMING_FAILED = 81 + ALARM_ID_TD_VALVE_TRANSITION_TIMEOUT = 82 + + ALARM_ID_RO_CAN_MESSAGE_NOT_ACKED_BY_DD = 83 + ALARM_ID_RO_FPGA_COMM_TIMEOUT = 84 + ALARM_ID_RO_COMM_TOO_MANY_BAD_CRCS = 85 + + Index: leahi_dialin/common/alarm_priorities.py =================================================================== diff -u --- leahi_dialin/common/alarm_priorities.py (revision 0) +++ leahi_dialin/common/alarm_priorities.py (revision 2138d06d100fdcf23f2e9069f35ee2fdee62008f) @@ -0,0 +1,60 @@ +########################################################################### +# +# Copyright (c) 2021-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 alarm_priorities.py +# +# @author (last) Quang Nguyen +# @date (last) 06-Jul-2021 +# @author (original) Peter Lucia +# @date (original) 05-Apr-2021 +# +############################################################################ +from enum import unique +from ..utils.base import DialinEnum + + +@unique +class AlarmPriorities(DialinEnum): + ALARM_PRIORITY_NONE = 0 # Indicates not an alarm or no alarms active at this time + ALARM_PRIORITY_LOW = 1 # Low priority alarm + ALARM_PRIORITY_MEDIUM = 2 # Medium priority alarm + ALARM_PRIORITY_HIGH = 3 # High priority alarm + NUM_OF_ALARM_PRIORITIES = 4 # Total number of alarm priorities + +class AlarmPriority: + ALARM_HIGH = 3 + ALARM_MED = 2 + ALARM_LOW = 1 + ALARM_NONE = 0 + + +class AlarmFlags: + ALARM_STATE_FLAG_BIT_POS_SYSTEM_FAULT = 0 + ALARM_STATE_FLAG_BIT_POS_STOP = 1 + ALARM_STATE_FLAG_BIT_POS_NO_CLEAR = 2 + ALARM_STATE_FLAG_BIT_POS_NO_RESUME = 3 + ALARM_STATE_FLAG_BIT_POS_NO_RINSEBACK = 4 + ALARM_STATE_FLAG_BIT_POS_NO_END_TREATMENT = 5 + ALARM_STATE_FLAG_BIT_POS_NO_NEW_TREATMENT = 6 + ALARM_STATE_FLAG_BIT_POS_USER_MUST_ACK = 7 + ALARM_STATE_FLAG_BIT_POS_ALARMS_TO_ESCALATE = 8 + ALARM_STATE_FLAG_BIT_POS_ALARMS_SILENCED = 9 + ALARM_STATE_FLAG_BIT_POS_LAMP_ON = 10 + ALARM_STATE_FLAG_BIT_POS_UNUSED_1 = 11 + ALARM_STATE_FLAG_BIT_POS_UNUSED_2 = 12 + ALARM_STATE_FLAG_BIT_POS_UNUSED_3 = 13 + ALARM_STATE_FLAG_BIT_POS_UNUSED_4 = 14 + ALARM_STATE_FLAG_BIT_POS_TOP_CONDITION = 15 + + +class AlarmDataTypes: + ALARM_DATA_TYPE_NONE = 0 # No data given. + ALARM_DATA_TYPE_U32 = 1 # Alarm data is unsigned 32-bit integer type. + ALARM_DATA_TYPE_S32 = 2 # Alarm data is signed 32-bit integer type. + ALARM_DATA_TYPE_F32 = 3 # Alarm data is 32-bit floating point type. + ALARM_DATA_TYPE_BOOL = 4 # Alarm data is 32-bit boolean type. + NUM_OF_ALARM_DATA_TYPES = 5 # Total number of alarm data types. Index: leahi_dialin/common/dd_defs.py =================================================================== diff -u --- leahi_dialin/common/dd_defs.py (revision 0) +++ leahi_dialin/common/dd_defs.py (revision 2138d06d100fdcf23f2e9069f35ee2fdee62008f) @@ -0,0 +1,327 @@ +########################################################################### +# +# Copyright (c) 2021-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 dd_defs.py +# +# @author (last) Dara Navaei +# @date (last) 08-Feb-2024 +# @author (original) Peter Lucia +# @date (original) 22-Jun-2021 +# +############################################################################ +from enum import unique +from ..utils.base import DialinEnum + + +@unique +class DDOpModes(DialinEnum): + DD_MODE_FAUL = 0 + DD_MODE_SERV = 1 + DD_MODE_INIT = 2 + DD_MODE_STAN = 3 + DD_MODE_GEND = 4 + DD_MODE_HEAT = 5 + DD_MODE_HCOL = 6 + DD_MODE_ROPS = 7 + DD_MODE_NLEG = 8 + NUM_OF_DD_MODES = 9 + +@unique +class DGInitStates(DialinEnum): + DG_POST_STATE_START = 0 + DG_POST_STATE_FW_COMPATIBILITY = 1 + DG_POST_STATE_FW_INTEGRITY = 2 + DG_POST_STATE_FPGA = 3 + DG_POST_STATE_RTC = 4 + DG_POST_STATE_NVDATAMGMT = 5 + DG_POST_STATE_TEMPERATURE_SENSORS = 6 + DG_POST_STATE_ACCELEROMETER = 7 + DG_POST_STATE_PRESSURES = 8 + DG_POST_STATE_RO_PUMP = 9 + DG_POST_STATE_DRAIN_PUMP = 10 + DG_POST_STATE_CONCENTRATE_PUMPS = 11 + DG_POST_STATE_CONDUCTIVITY_SENSORS = 12 + DG_POST_STATE_RESERVOIRS = 13 + DG_POST_STATE_UV_REACTORS = 14 + DG_POST_STATE_THERMISTORS = 15 + DG_POST_STATE_FANS = 16 + DG_POST_STATE_DIALYSATE_FLOW_SENSOR = 17 + DG_POST_STATE_WATCHDOG = 18 + DG_POST_STATE_SAFETY_SHUTDOWN = 19 + DG_POST_STATE_LOAD_CELL = 20 + DG_POST_STATE_COMPLETED = 21 + DG_POST_STATE_FAILED = 22 + NUM_OF_DG_POST_STATES = 23 + + +@unique +class DGFaultStates(DialinEnum): + DG_FAULT_STATE_START = 0 # DG fault start state + DG_FAULT_STATE_RUN_NV_POSTS = 1 # DG fault run RTC and NV data management post + DG_FAULT_STATE_COMPLETE = 2 # DG fault complete + NUM_OF_DG_FAULT_STATES = 3 # Number of fault mode states + +@unique +class DGSoloStates(DialinEnum): + DG_SOLO_STANDBY_STATE_START = 0 + DG_SOLO_IDLE_STATE = 1 + NUM_OF_DG_SOLO_STANDBY_STATES = 2 + + +@unique +class DGStandByModeStates(DialinEnum): + DG_STANDBY_MODE_STATE_IDLE = 0 # Idle standby mode state + DG_STANDBY_MODE_STATE_FLUSH_FILTER = 1 # Sample water flush filter state + DG_STANDBY_MODE_STATE_FLUSH_FILTER_IDLE = 2 # Sample water flush filter idle state + DG_STANDBY_MODE_STATE_SAMPLE_WATER = 3 # Sample water state + DG_STANDBY_MODE_STATE_PAUSE = 4 # Pause state + NUM_OF_DG_STANDBY_MODE_STATES = 5 # Number of standby mode states + + +@unique +class DGGenIdleModeStates(DialinEnum): + DG_GEN_IDLE_MODE_STATE_START = 0 + DG_GEN_IDLE_MODE_STATE_FLUSH_WATER = 1 + DG_GEN_IDLE_MODE_STATE_HANDLE_BAD_FILL = 2 + NUM_OF_DG_GEN_IDLE_MODE_STATES = 3 + + +@unique +class DGGenIdleModeBadFillSubStates(DialinEnum): + DG_HANDLE_BAD_FILL_STATE_START = 0 + DG_HANDLE_BAD_FILL_STATE_FIRST_DRAIN = 1 + DG_HANDLE_BAD_FILL_STATE_FLUSH_FILL = 2 + DG_HANDLE_BAD_FILL_STATE_SECOND_DRAIN = 3 + DG_HANDLE_BAD_FILL_STATE_REFILL = 4 + NUM_OF_DG_HANDLE_BAD_FILL_STATES = 6 + + +@unique +class DGFillModeStates(DialinEnum): + DG_FILL_MODE_STATE_TEST_INLET_WATER = 0 # Test inlet water quality state + DG_FILL_MODE_STATE_PRIME_CONCENTRATE_LINES = 1 # Prime the acid and bicarb concentrate lines + DG_FILL_MODE_STATE_FLUSH_BUBBLES = 2 # Flush the bubbles in the lines state + DG_FILL_MODE_STATE_TEST_BICARB_CONDUCTIVITY = 3 # Test the conductivity range of the bicarb concentrate state + DG_FILL_MODE_STATE_TEST_ACID_CONDUCTIVITY = 4 # Test the conductivity range of the acid concentrate state + DG_FILL_MODE_STATE_PRODUCE_DIALYSATE = 5 # Dialysate production state + DG_FILL_MODE_STATE_DELIVER_DIALYSATE = 6 # Dialysate deliver state + DG_FILL_MODE_STATE_PAUSED = 7 # Dialysate generation pause state + NUM_OF_DG_FILL_MODE_STATES = 8 # Number of fill mode states + + +@unique +class DGDrainModeStates(DialinEnum): + DG_DRAIN_STATE_START = 0 # Start drain mode state + DG_DRAIN_STATE_DRAIN = 1 # Drain drain mode state + DG_DRAIN_STATE_TARE = 2 # Tare drain mode state + DG_DRAIN_STATE_RINSE = 3 + NUM_OF_DG_DRAIN_STATES = 4 # Number of drain mode states + + +@unique +class DGFlushStates(DialinEnum): + DG_FLUSH_STATE_START = 0 + DG_FLUSH_STATE_DRAIN_R1 = 1 + DG_FLUSH_STATE_DRAIN_R2 = 2 + DG_FLUSH_STATE_FLUSH_DRAIN = 3 + DG_FLUSH_STATE_FLUSH_DIALYSATE = 4 + DG_FLUSH_STATE_FLUSH_CONCENTRATE_STRAWS = 5 + DG_FLUSH_STATE_FLUSH_R1_TO_R2 = 6 + DG_FLUSH_STATE_FLUSH_R2_AND_DRAIN_R1 = 7 + DG_FLUSH_STATE_FLUSH_CIRCULATION_DRAIN_LINE = 8 + DG_FLUSH_STATE_FLUSH_CIRCULATION = 9 + DG_FLUSH_STATE_FLUSH_WITH_FRESH_WATER = 10 + DG_FLUSH_STATE_CANCEL_BASIC_PATH = 11 + DG_FLUSH_STATE_CANCEL_WATER_PATH = 12 + DG_FLUSH_STATE_COMPLETE = 13 + NUM_OF_DG_FLUSH_STATES = 14 + + +@unique +class DGFlushUIStates(DialinEnum): + FLUSH_UI_STATE_NOT_RUNNING = 0 + FLUSH_UI_STATE_NOT_DRAIN_DEVICE = 1 + FLUSH_UI_STATE_NOT_FLUSH_RESERVOIRS = 2 + FLUSH_UI_STATE_NOT_DRAIN_RESERVOIRS = 3 + FLUSH_UI_STATE_NOT_FLUSH_RECIRCULATION_PATH = 4 + FLUSH_UI_STATE_NOT_CANCEL_FLUSH = 5 + FLUSH_UI_STATE_NOT_COMPLETE = 6 + NUM_OF_FLUSH_UI_STATES = 7 + + +@unique +class DGHeatDisinfectStates(DialinEnum): + DG_HEAT_DISINFECT_STATE_START = 0 # Heat disinfect, start mode state + DG_HEAT_DISINFECT_STATE_DRAIN_R1 = 1 # Heat disinfect, drain R1 state + DG_HEAT_DISINFECT_STATE_DRAIN_R2 = 2 # Heat disinfect, drain R2 state + DG_HEAT_DISINFECT_STATE_FLUSH_DRAIN = 3 # Heat disinfect, flush drain state + DG_HEAT_DISINFECT_STATE_FLUSH_CIRCULATION = 4 # Heat disinfect, flush circulation state + DG_HEAT_DISINFECT_STATE_FLUSH_R1_AND_R2 = 5 # Heat disinfect, flush R1 and R2 state + DG_HEAT_DISINFECT_STATE_FLUSH_R2_AND_DRAIN_R1 = 6 # Heat disinfect, flush R2 and drain R1 state + DG_HEAT_DISINFECT_STATE_FLUSH_DRAIN_R2 = 7 # Heat disinfect, flush drain R2 state + DG_HEAT_DISINFECT_STATE_FLUSH_DRAIN_R1 = 8 # Heat disinfect, flush drain R1 state + DG_HEAT_DISINFECT_STATE_FILL_WITH_WATER = 9 # Heat disinfect, fill with water state + DG_HEAT_DISINFECT_STATE_DISINFECT_R1_TO_R2 = 10 # Heat disinfect, disinfect R1 to R2 state + DG_HEAT_DISINFECT_STATE_PREPARE_FOR_HOT_WATER_TRANSITION = 11 # Heat disinfect, prepare for hot water transition + DG_HEAT_DISINFECT_STATE_FILL_R2_WITH_HOT_WATER = 12 # Heat disinfect, fill R2 with hot water state + DG_HEAT_DISINFECT_STATE_DISINFECT_R2_TO_R1 = 13 # Heat disinfect, disinfect R2 to R1 state + DG_HEAT_DISINFECT_STATE_COOL_DOWN_HEATERS = 14 # Heat disinfect, cool down heaters state + DG_HEAT_DISINFECT_STATE_MIX_DRAIN_R1 = 15 # Heat disinfect, mix drain R1 state + DG_HEAT_DISINFECT_STATE_MIX_DRAIN_R2 = 16 # Heat disinfect, mix drain R2 state + DG_HEAT_DISINFECT_STATE_CANCEL_BASIC_PATH = 17 # Heat disinfect, cancel mode basic path state + DG_HEAT_DISINFECT_STATE_CANCEL_WATER_PATH = 18 # Heat disinfect, cancel mode water path state + DG_HEAT_DISINFECT_STATE_COMPLETE = 19 # Heat disinfect, complete state + DG_NELSON_HEAT_DISINFECT_STATE_FILL_R1_WITH_WATER = 20 # Heat disinfect, Nelson support state + NUM_OF_DG_HEAT_DISINFECT_STATES = 21 # Number of heat disinfect mode states + + +@unique +class DGHeatDisinfectUIStates(DialinEnum): + HEAT_DISINFECT_UI_STATE_NOT_RUNNING = 0 + HEAT_DISINFECT_UI_STATE_FLUSH_BEFORE_DISINFECT = 1 + HEAT_DISINFECT_UI_STATE_HEAT_UP_WATER = 2 + HEAT_DISINFECT_UI_STATE_DISINFECT_RESERVOIR_1 = 3 + HEAT_DISINFECT_UI_STATE_TRANSITION_HOT_WATER = 4 + HEAT_DISINFECT_UI_STATE_DISINFECT_RESERVOIR_2 = 5 + HEAT_DISINFECT_UI_STATE_COOL_DOWN_DEVICE = 6 + HEAT_DISINFECT_UI_STATE_FLUSH_AFTER_DISINFECT = 7 + HEAT_DISINFECT_UI_STATE_CANCEL_DISINFECT = 8 + HEAT_DISINFECT_UI_STATE_COMPLETE = 9 + NUM_OF_HEAT_DISINFECT_UI_STATES = 10 + + +@unique +class DGHeatDisinfectActiveCoolStates(DialinEnum): + DG_HEAT_DISINFECT_ACTIVE_COOL_STATE_START = 0 + DG_HEAT_DISINFECT_ACTIVE_COOL_MIX_DRAIN_R1_STATE = 1 + DG_HEAT_DISINFECT_ACTIVE_COOL_MIX_DRAIN_R2_STATE = 2 + DG_HEAT_DISINFECT_ACTIVE_COOL_FILL_R1_STATE = 3 + DG_HEAT_DISINFECT_ACTIVE_COOL_FILL_R2_STATE = 4 + DG_HEAT_DISINFECT_ACTIVE_COOL_DRAIN_R2_FILL_R1_TO_R2_STATE = 5 + DG_HEAT_DISINFECT_ACTIVE_COOL_DRAIN_R1_FILL_R2_TO_R1_STATE = 6 + DG_HEAT_DISINFECT_ACTIVE_COOL_DRAIN_R1_STATE = 7 + DG_HEAT_DISINFECT_ACTIVE_COOL_DRAIN_R2_STATE = 8 + DG_HEAT_DISINFECT_ACTIVE_COOL_CANCEL_WATER_PATH_STATE = 9 + DG_HEAT_DISINFECT_ACTIVE_COOL_CANCEL_BASIC_PATH_STATE = 10 + DG_HEAT_DISINFECT_ACTIVE_COOL_STATE_COMPLETE = 11 + + +@unique +class DGChemicalDisinfectStates(DialinEnum): + DG_CHEM_DISINFECT_STATE_START = 0 + DG_CHEM_DISINFECT_STATE_DRAIN_R1 = 1 + DG_CHEM_DISINFECT_STATE_DRAIN_R2 = 2 + DG_CHEM_DISINFECT_STATE_FLUSH_DRAIN = 3 + DG_CHEM_DISINFECT_STATE_DEPRIME_ACID_LINE = 4 + DG_CHEM_DISINFECT_STATE_FLUSH_CIRCULATION = 5 + DG_CHEM_DISINFECT_STATE_PRIME_DISINFECTANT = 6 + DG_CHEM_DISINFECT_STATE_DISINFECTANT_FLUSH = 7 + DG_CHEM_DISINFECT_STATE_FILL_WITH_DISINFECTANT = 8 + DG_CHEM_DISINFECT_STATE_DISINFECT_R1_TO_R2 = 9 + DG_CHEM_DISINFECT_STATE_PARTIAL_DRAIN_R1_FILL_R2_TO_R1 = 10 + DG_CHEM_DISINFECT_STATE_DISINFECT_R2_TO_R1 = 11 + DG_CHEM_DISINFECT_STATE_PARTIAL_DRAIN_R2_FILL_R1_TO_R2 = 12 + DG_CHEM_DISINFECT_STATE_CANCEL_BASIC_PATH = 13 + DG_CHEM_DISINFECT_STATE_CANCEL_WATER_PATH = 14 + DG_CHEM_DISINFECT_STATE_COMPLETE = 15 + NUM_OF_DG_CHEM_DISINFECT_STATES = 16 + + +@unique +class DGChemDisinfectFlushStates(DialinEnum): + DG_CHEM_DISINFECT_FLUSH_STATE_START = 0 + DG_CHEM_DISINFECT_FLUSH_STATE_DRAIN_R1 = 1 + DG_CHEM_DISINFECT_FLUSH_STATE_DRAIN_R2 = 2 + DG_CHEM_DISINFECT_FLUSH_STATE_FLUSH_DRAIN = 3 + DG_CHEM_DISINFECT_FLUSH_STATE_FLUSH_DISINFECTANT_LINE = 4 + DG_CHEM_DISINFECT_FLUSH_STATE_FLUSH_UF = 5 + DG_CHEM_DISINFECT_FLUSH_STATE_FLUSH_R2_TO_R1_DRAIN_R1 = 6 + DG_CHEM_DISINFECT_FLUSH_STATE_FLUSH_R1_TO_R2_DRAIN_R2 = 7 + DG_CHEM_DISINFECT_FLUSH_STATE_SAMPLE_FLUSH_R1_TO_R2_DRAIN_R2 = 8 + DG_CHEM_DISINFECT_FLUSH_STATE_CANCEL_BASIC_PATH = 9 + DG_CHEM_DISINFECT_FLUSH_STATE_CANCEL_WATER_PATH = 10 + DG_CHEM_DISINFECT_FLUSH_STATE_COMPLETE = 11 + NUM_OF_DG_CHEM_DISINFECT_FLUSH_STATES = 12 + + +@unique +class DGChemDisinfectFlushUIStates(DialinEnum): + CHEM_DISINFECT_FLUSH_UI_STATE_NOT_RUNNING = 0 + CHEM_DISINFECT_FLUSH_UI_STATE_FLUSH_AFTER_DISINFECT = 1 + CHEM_DISINFECT_FLUSH_UI_STATE_CANCEL_FLUSH = 2 + CHEM_DISINFECT_FLUSH_UI_STATE_COMPLETE = 3 + NUM_OF_CHEM_DISINFECT_FLUSH_UI_STATES = 4 + + +@unique +class DGChemDisinfectUIStates(DialinEnum): + CHEM_DISINFECT_UI_STATE_NOT_RUNNING = 0 + CHEM_DISINFECT_UI_STATE_FLUSH_BEFORE_DISINFECT = 1 + CHEM_DISINFECT_UI_STATE_MIX_WATER_AND_ACID = 2 + CHEM_DISINFECT_UI_STATE_REMOVE_ACID = 3 + CHEM_DISINFECT_UI_STATE_DISINFECT_DEVICE = 4 + CHEM_DISINFECT_UI_STATE_FLUSH_AFTER_DISINFECT = 5 + CHEM_DISINFECT_UI_STATE_CANCEL_DISINFECT = 6 + CHEM_DISINFECT_UI_STATE_COMPLETE = 7 + NUM_OF_CHEM_DISINFECT_UI_STATES = 8 + + +@unique +class DGROPermeateSampleStates(DialinEnum): + DG_RO_PERM_SAMPLE_STATE_START = 0 + DG_RO_PERM_SAMPLE_STATE_DRAIN_R1 = 1 + DG_RO_PERM_SAMPLE_STATE_DRAIN_R2 = 2 + DG_RO_PERM_SAMPLE_STATE_FLUSH_DRAIN = 3 + DG_RO_PERM_SAMPLE_STATE_FLUSH_DIALYSATE = 4 + DG_RO_PERM_SAMPLE_STATE_FLUSH_CONCENTRATE_STRAWS = 5 + DG_RO_PERM_SAMPLE_STATE_FLUSH_R2_TO_R1_AND_DRAIN_R1 = 6 + DG_RO_PERM_SAMPLE_STATE_COLLECT_SAMPLE = 7 + DG_RO_PERM_SAMPLE_STATE_CANCEL_BASIC_PATH = 8 + DG_RO_PERM_SAMPLE_STATE_CANCEL_WATER_PATH = 9 + DG_RO_PERM_SAMPLE_STATE_COMPLETE = 10 + NUM_OF_DG_RO_PERM_STATES = 11 + + +@unique +class DGEventList(DialinEnum): + DG_EVENT_STARTUP = 0 # DG startup event + DG_EVENT_OP_MODE_CHANGE = 1 # DG Op mode change event + DG_EVENT_SUB_MODE_CHANGE = 2 # DG Op sub-mode change event + DG_EVENT_CONCENTRATE_CAP_SWITCH_CHANGE = 3 # DG concentrate cap switch change + DG_EVENT_DIALYSATE_CAP_SWITCH_CHANGE = 4 # DG dialysate cap switch change + DG_EVENT_CPU_RAM_ERROR_STATUS = 5 # DG processor RAM error + DG_EVENT_CAL_RECORD_UPDATE = 6 # DG new calibration record updated + DG_EVENT_SYSTEM_RECORD_UPDATE = 7 # DG new system record has been updated + DG_EVENT_SERVICE_UPDATE = 8 # DG new service record has been updated + DG_EVENT_USAGE_INFO_UPDATE = 9 # DG new usage information has been updated + DG_EVENT_SW_CONFIG_UPDATE = 10 # DG new software configuration has been updated + DG_EVENT_SCHEDULED_RUNS_UPDATE = 11 # DG new scheduled runs information has been updated + DG_EVENT_HEATERS_INFO_UPDATE = 12 # DG new heaters information has been updated + DG_EVENT_AVG_DIALYSATE_FILL_COND_VALUES = 13 # DG average dialysate fill conductivity values + DG_EVENT_RESERVOIR_FILL_VALUES = 14 # DG reservoir fill base reservoir weight and filled volume values + DG_EVENT_OPERATION_STATUS = 15 # DG operation status event + DG_EVENT_TEMPERATURE_DRIFT = 16 # DG temperature drift event + DG_EVENT_BICARB_CHECK_RESULT = 17 # DG bicarb check result + DG_EVENT_ACID_CHECK_RESULT = 18 # DG acid check result + DG_EVENT_COND1_VS_COND2_DIFF_RESULT = 19 # DG CD1 (acid) vs. CD2 (bicarb) different result + NUM_OF_DG_EVENT_IDS = 20 # Total number of DG events + + +@unique +class DGEventDataType(DialinEnum): + EVENT_DATA_TYPE_NONE = 0 + EVENT_DATA_TYPE_U32 = 1 + EVENT_DATA_TYPE_S32 = 2 + EVENT_DATA_TYPE_F32 = 3 + EVENT_DATA_TYPE_BOOL = 4 + NUM_OF_EVENT_DATA_TYPES = 5 + + +@unique +class DGServiceModesStates(DialinEnum): + DG_SERVICE_STATE_START = 0 # Start service mode state + NUM_OF_DG_SERVICE_STATES = 1 # Number of service mode states Index: leahi_dialin/common/msg_defs.py =================================================================== diff -u --- leahi_dialin/common/msg_defs.py (revision 0) +++ leahi_dialin/common/msg_defs.py (revision 2138d06d100fdcf23f2e9069f35ee2fdee62008f) @@ -0,0 +1,136 @@ +########################################################################### +# +# 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 msg_defs.py +# +# @author (last) Dara Navaei +# @date (last) 08-May-2024 +# @author (original) Peter Lucia +# @date (original) 07-Aug-2020 +# +############################################################################ +from enum import unique +from ..utils.base import DialinEnum +from .msg_ids import MsgIds + + +ACK_NOT_REQUIRED = [ + +] + + +@unique +class RequestRejectReasons(DialinEnum): + REQUEST_REJECT_REASON_NONE = 0 + REQUEST_REJECT_REASON_NOT_ALLOWED_IN_CURRENT_MODE = 1 + REQUEST_REJECT_REASON_TIMEOUT_WAITING_FOR_USER_CONFIRM = 2 + REQUEST_REJECT_REASON_NOT_IN_TREATMENT_MODE = 3 + REQUEST_REJECT_REASON_INVALID_TREATMENT_STATE = 4 + REQUEST_REJECT_REASON_TREATMENT_TOO_CLOSE_TO_FINISHED = 5 + REQUEST_REJECT_REASON_TREATMENT_TIME_OUT_OF_RANGE = 6 + REQUEST_REJECT_REASON_TREATMENT_TIME_LESS_THAN_CURRENT = 7 + REQUEST_REJECT_REASON_BLOOD_FLOW_OUT_OF_RANGE = 8 + REQUEST_REJECT_REASON_DIAL_FLOW_OUT_OF_RANGE = 9 + REQUEST_REJECT_REASON_DIAL_VOLUME_OUT_OF_RANGE = 10 + REQUEST_REJECT_REASON_UF_VOLUME_OUT_OF_RANGE = 11 + REQUEST_REJECT_REASON_UF_RATE_OUT_OF_RANGE = 12 + REQUEST_REJECT_REASON_TREATMENT_TIME_LESS_THAN_MINIMUM = 13 + REQUEST_REJECT_REASON_UF_NOT_IN_PROGESS = 14 + REQUEST_REJECT_REASON_UF_NOT_PAUSED = 15 + REQUEST_REJECT_REASON_SALINE_BOLUS_IN_PROGRESS = 16 + REQUEST_REJECT_REASON_PARAM_OUT_OF_RANGE = 17 + REQUEST_REJECT_REASON_FILTER_FLUSH_HAS_BEEN_EXPIRED = 18 + REQUEST_REJECT_REASON_SALINE_MAX_VOLUME_REACHED = 19 + REQUEST_REJECT_REASON_SALINE_BOLUS_NOT_IN_PROGRESS = 20 + REQUEST_REJECT_REASON_ACTION_DISABLED_IN_CURRENT_STATE = 21 + REQUEST_REJECT_REASON_ALARM_IS_ACTIVE = 22 + REQUEST_REJECT_REASON_INVALID_COMMAND = 23 + REQUEST_REJECT_REASON_TREATMENT_IS_COMPLETED = 24 + REQUEST_REJECT_REASON_ADDL_RINSEBACK_MAX_VOLUME_REACHED = 25 + REQUEST_REJECT_REASON_DIALYZER_NOT_INVERTED = 26 + REQUEST_REJECT_REASON_NO_PATIENT_CONNECTION_CONFIRM = 27 + REQUEST_REJECT_REASON_DD_COMM_LOST = 28 + REQUEST_REJECT_REASON_DD_NOT_IN_STANDBY_IDLE_STATE = 29 + REQUEST_REJECT_REASON_INVALID_REQUEST_FORMAT = 30 + REQUEST_REJECT_REASON_INVALID_DATE_OR_TIME = 31 + REQUEST_REJECT_REASON_TREATMENT_IN_PROGRESS = 32 + REQUEST_REJECT_REASON_BATTERY_IS_NOT_CHARGED = 33 + REQUEST_REJECT_REASON_RINSEBACK_NOT_COMPLETED = 34 + REQUEST_REJECT_REASON_DOOR_NOT_CLOSED = 35 + REQUEST_REJECT_REASON_DD_DIALYSATE_CAP_OPEN = 36 + REQUEST_REJECT_REASON_DD_CONCENTRATE_CAP_OPEN = 37 + REQUEST_REJECT_REASON_DISINFECT_HAS_BEEN_EXPIRED = 38 + REQUEST_REJECT_REASON_DD_SERVICE_IS_DUE = 39 + REQUEST_REJECT_REASON_TD_SERVICE_IS_DUE = 40 + REQUEST_REJECT_REASON_RO_FILTER_TEMPERATURE_OUT_OF_RANGE = 41 + REQUEST_REJECT_REASON_DD_INCOMPATIBLE = 42 + REQUEST_REJECT_REASON_DIALYZER_REPRIME_IN_PROGRESS = 43 + REQUEST_REJECT_REASON_RO_ONLY_MODE_DD_BUSY = 44 + REQUEST_REJECT_REASON_RO_ONLY_MODE_INVALID_PARAMETER = 45 + REQUEST_REJECT_REASON_RO_ONLY_MODE_INVALID_PAYLOAD_LENGTH = 46 + REQUEST_REJECT_REASON_TREATMENT_CANNOT_BE_RESUMED = 47 + +class MsgFieldPositions: + # Generic response msg field byte positions (where 32-bit data fields are used) + START_POS_FIELD_1 = 6 # Hardcoded for now to avoid cyclic import issue. See protocols.CAN.DenaliMessage class + END_POS_FIELD_1 = START_POS_FIELD_1 + 4 + START_POS_FIELD_2 = END_POS_FIELD_1 + END_POS_FIELD_2 = START_POS_FIELD_2 + 4 + START_POS_FIELD_3 = END_POS_FIELD_2 + END_POS_FIELD_3 = START_POS_FIELD_3 + 4 + START_POS_FIELD_4 = END_POS_FIELD_3 + END_POS_FIELD_4 = START_POS_FIELD_4 + 4 + START_POS_FIELD_5 = END_POS_FIELD_4 + END_POS_FIELD_5 = START_POS_FIELD_5 + 4 + START_POS_FIELD_6 = END_POS_FIELD_5 + END_POS_FIELD_6 = START_POS_FIELD_6 + 4 + START_POS_FIELD_7 = END_POS_FIELD_6 + END_POS_FIELD_7 = START_POS_FIELD_7 + 4 + START_POS_FIELD_8 = END_POS_FIELD_7 + END_POS_FIELD_8 = START_POS_FIELD_8 + 4 + START_POS_FIELD_9 = END_POS_FIELD_8 + END_POS_FIELD_9 = START_POS_FIELD_9 + 4 + START_POS_FIELD_10 = END_POS_FIELD_9 + END_POS_FIELD_10 = START_POS_FIELD_10 + 4 + START_POS_FIELD_11 = END_POS_FIELD_10 + END_POS_FIELD_11 = START_POS_FIELD_11 + 4 + START_POS_FIELD_12 = END_POS_FIELD_11 + END_POS_FIELD_12 = START_POS_FIELD_12 + 4 + START_POS_FIELD_13 = END_POS_FIELD_12 + END_POS_FIELD_13 = START_POS_FIELD_13 + 4 + START_POS_FIELD_14 = END_POS_FIELD_13 + END_POS_FIELD_14 = START_POS_FIELD_14 + 4 + START_POS_FIELD_15 = END_POS_FIELD_14 + END_POS_FIELD_15 = START_POS_FIELD_15 + 4 + START_POS_FIELD_16 = END_POS_FIELD_15 + END_POS_FIELD_16 = START_POS_FIELD_16 + 4 + START_POS_FIELD_17 = END_POS_FIELD_16 + END_POS_FIELD_17 = START_POS_FIELD_17 + 4 + START_POS_FIELD_18 = END_POS_FIELD_17 + END_POS_FIELD_18 = START_POS_FIELD_18 + 4 + START_POS_FIELD_19 = END_POS_FIELD_18 + END_POS_FIELD_19 = START_POS_FIELD_19 + 4 + START_POS_FIELD_20 = END_POS_FIELD_19 + END_POS_FIELD_20 = START_POS_FIELD_20 + 4 + START_POS_FIELD_21 = END_POS_FIELD_20 + END_POS_FIELD_21 = START_POS_FIELD_21 + 4 + START_POS_FIELD_22 = END_POS_FIELD_21 + END_POS_FIELD_22 = START_POS_FIELD_22 + 4 + START_POS_FIELD_23 = END_POS_FIELD_22 + END_POS_FIELD_23 = START_POS_FIELD_23 + 4 + START_POS_FIELD_24 = END_POS_FIELD_23 + END_POS_FIELD_24 = START_POS_FIELD_24 + 4 + START_POS_FIELD_25 = END_POS_FIELD_24 + END_POS_FIELD_25 = START_POS_FIELD_25 + 4 + START_POS_FIELD_26 = END_POS_FIELD_25 + END_POS_FIELD_26 = START_POS_FIELD_26 + 4 + START_POS_FIELD_27 = END_POS_FIELD_26 + END_POS_FIELD_27 = START_POS_FIELD_27 + 4 + START_POS_FIELD_28 = END_POS_FIELD_27 + END_POS_FIELD_28 = START_POS_FIELD_28 + 4 + START_POS_FIELD_29 = END_POS_FIELD_28 + END_POS_FIELD_29 = START_POS_FIELD_29 + 4 Index: leahi_dialin/common/msg_ids.py =================================================================== diff -u --- leahi_dialin/common/msg_ids.py (revision 0) +++ leahi_dialin/common/msg_ids.py (revision 2138d06d100fdcf23f2e9069f35ee2fdee62008f) @@ -0,0 +1,171 @@ +########################################################################### +# +# Copyright (c) 2021-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 msg_ids.py +# +# @author (last) Dara Navaei +# @date (last) 10-May-2024 +# @author (original) Peter Lucia +# @date (original) 06-Apr-2021 +# +############################################################################ +from enum import unique +from ..utils.base import DialinEnum + + +# Branch: staging +@unique +class MsgIds(DialinEnum): + MSG_ID_UNUSED = 0x0 + MSG_ID_ALARM_STATUS_DATA = 0x1 + MSG_ID_ALARM_TRIGGERED = 0x2 + MSG_ID_ALARM_CLEARED = 0x3 + MSG_ID_ALARM_CONDITION_CLEARED = 0x4 + MSG_ID_USER_ALARM_SILENCE_REQUEST = 0x5 + MSG_ID_UI_ALARM_USER_ACTION_REQUEST = 0x6 + MSG_ID_TD_ALARM_INFORMATION_DATA = 0x7 + MSG_ID_DD_ALARM_INFO_DATA = 0x8 + MSG_ID_UI_ACTIVE_ALARMS_LIST_REQUEST = 0x9 + MSG_ID_TD_ACTIVE_ALARMS_LIST_REQUEST_RESPONSE = 0xA + MSG_ID_UI_SET_ALARM_AUDIO_VOLUME_LEVEL_CMD_REQUEST = 0xB + MSG_ID_TD_ALARM_AUDIO_VOLUME_SET_RESPONSE = 0xC + MSG_ID_FW_VERSIONS_REQUEST = 0xD + MSG_ID_TD_VERSION_REPONSE = 0xE + MSG_ID_DD_VERSION_REPONSE = 0xF + MSG_ID_UI_CHECK_IN = 0x10 + MSG_ID_TD_BLOOD_PUMP_DATA = 0x11 + MSG_ID_TD_OP_MODE_DATA = 0x12 + MSG_ID_DD_OP_MODE_DATA = 0x13 + MSG_ID_DD_COMMAND_RESPONSE = 0x14 + MSG_ID_TD_UI_VERSION_INFO_REQUEST = 0x15 + MSG_ID_UI_VERSION_INFO_RESPONSE = 0x16 + MSG_ID_TD_EVENT = 0x17 + MSG_ID_DD_EVENT = 0x18 + MSG_ID_TD_DD_ALARMS_REQUEST = 0x19 + MSG_ID_UI_TD_RESET_IN_SERVICE_MODE_REQUEST = 0x1A + MSG_ID_DD_VALVES_STATES_DATA = 0x1B + MSG_ID_DD_PRESSURES_DATA = 0x1C + MSG_ID_TD_VOLTAGES_DATA = 0x1D + MSG_ID_TD_BUBBLES_DATA = 0x1E + MSG_ID_DD_CONDUCTIVITY_DATA = 0x1F + MSG_ID_TD_AIR_PUMP_DATA = 0x20 + MSG_ID_TD_SWITCHES_DATA = 0x21 + MSG_ID_POWER_OFF_WARNING = 0x22 + MSG_ID_OFF_BUTTON_PRESS_REQUEST = 0x23 + MSG_ID_PRESSURE_DATA = 0x24 + MSG_ID_DD_CONCENTRATE_PUMP_DATA = 0x25 + MSG_ID_DD_TEMPERATURE_DATA = 0x26 + MSG_ID_DIALYSATE_PUMPS_DATA = 0x27 + MSG_ID_DD_HEATERS_DATA = 0x28 + MSG_ID_DD_LEVEL_DATA = 0x29 + MSG_ID_TD_AIR_TRAP_DATA = 0x2A + MSG_ID_TD_VALVES_DATA = 0x2B + MSG_ID_RO_EVENT = 0x2C + MSG_ID_RO_ALARM_INFO_DATA= 0x2D + MSG_ID_DD_BAL_CHAMBER_DATA = 0x2E + MSG_ID_DD_GEN_DIALYSATE_MODE_DATA = 0x2F + MSG_ID_DD_GEN_DIALYSATE_REQUEST_DATA = 0x30 + + + MSG_ID_TESTER_LOGIN_REQUEST = 0x8000 + MSG_ID_TD_SOFTWARE_RESET_REQUEST = 0x8001 + MSG_ID_TD_SEND_TEST_CONFIGURATION = 0x8002 + MSG_ID_TD_BUBBLE_OVERRIDE_REQUEST = 0x8003 + MSG_ID_TD_VOLTAGE_PUBLISH_INTERVAL_OVERRIDE_REQUEST = 0x8004 + MSG_ID_TD_VOLTAGE_OVERRIDE_REQUEST = 0x8005 + MSG_ID_TD_BUBBLE_PUBLISH_INTERVAL_OVERRIDE_REQUEST = 0x8006 + MSG_ID_TD_PRESSURE_OVERRIDE_REQUEST = 0x8007 + MSG_ID_TD_AIR_PUMP_SET_STATE_REQUEST = 0x8008 + MSG_ID_TD_AIR_PUMP_PUBLISH_INTERVAL_OVERRIDE_REQUEST = 0x8009 + MSG_ID_TD_SWITCHES_PUBLISH_INTERVAL_OVERRIDE_REQUEST = 0x800A + MSG_ID_TD_SWITCH_STATE_OVERRIDE_REQUEST = 0x800B + MSG_ID_TD_OFF_BUTTON_OVERRIDE_REQUEST = 0x800C + MSG_ID_TD_STOP_BUTTON_OVERRIDE_REQUEST = 0x800D + MSG_ID_TD_ALARM_LAMP_PATTERN_OVERRIDE_REQUEST = 0x800E + MSG_ID_TD_ALARM_AUDIO_LEVEL_OVERRIDE_REQUEST = 0x800F + MSG_ID_TD_ALARM_AUDIO_CURRENT_HG_OVERRIDE_REQUEST = 0x8010 + MSG_ID_TD_ALARM_AUDIO_CURRENT_LG_OVERRIDE_REQUEST = 0x8011 + MSG_ID_TD_BACKUP_ALARM_AUDIO_CURRENT_OVERRIDE_REQUEST = 0x8012 + MSG_ID_TD_PRESSURE_PUBLISH_INTERVAL_OVERRIDE_REQUEST = 0x8013 + MSG_ID_TD_AIR_TRAP_LEVEL_OVERRIDE_REQUEST = 0x8014 + MSG_ID_TD_AIR_TRAP_LEVEL_RAW_OVERRIDE_REQUEST = 0x8015 + MSG_ID_TD_AIR_TRAP_PUBLISH_INTERVAL_OVERRIDE_REQUEST = 0x8016 + MSG_ID_TD_2_WAY_VALVE_SET_STATE_REQUEST = 0x8017 + MSG_ID_TD_ROTARY_PINCH_VALVE_SET_POS_REQUEST = 0x8018 + MSG_ID_TD_ROTARY_PINCH_VALVE_STATUS_OVERRIDE_REQUEST = 0x8019 + MSG_ID_TD_ROTARY_PINCH_VALVE_POSITION_OVERRIDE_REQUEST = 0x801A + MSG_ID_TD_VALVES_PUBLISH_INTERVAL_OVERRIDE_REQUEST = 0x801B + MSG_ID_TD_ALARM_STATUS_PUBLISH_INTERVAL_OVERRIDE_REQUEST = 0x801C + MSG_ID_TD_ALARM_INFO_PUBLISH_INTERVAL_OVERRIDE_REQUEST = 0x801D + MSG_ID_TD_ALARM_START_TIME_OVERRIDE_REQUEST = 0x801E + MSG_ID_TD_ALARM_CLEAR_ALL_ALARMS_REQUEST = 0x801F + MSG_ID_TD_WATCHDOG_OVERRIDE_REQUEST = 0x8020 + MSG_ID_TD_ALARM_STATE_OVERRIDE_REQUEST = 0x8021 + MSG_ID_TD_SAFETY_SHUTDOWN_OVERRIDE_REQUEST = 0x8022 + MSG_ID_TD_PINCH_VALVE_SET_POSITION_REQUEST = 0x8023 + MSG_ID_TD_PINCH_VALVE_HOME_REQUEST = 0x8024 + MSG_ID_TD_BLOOD_PUMP_PUBLISH_INTERVAL_OVERRIDE_REQUEST = 0x8025 + MSG_ID_TD_BLOOD_PUMP_SET_FLOW_RATE_REQUEST = 0x8026 + MSG_ID_TD_BLOOD_PUMP_SET_SPEED_REQUEST = 0x8027 + MSG_ID_TD_BLOOD_PUMP_MEASURED_FLOW_RATE_OVERRIDE_REQUEST = 0x8028 + MSG_ID_TD_BLOOD_PUMP_MEASURED_MOTOR_SPEED_OVERRIDE_REQUEST = 0x8029 + MSG_ID_TD_BLOOD_PUMP_MEASURED_ROTOR_SPEED_OVERRIDE_REQUEST = 0x802A + MSG_ID_TD_BLOOD_PUMP_ROTOR_COUNT_OVERRIDE_REQUEST = 0x802B + + MSG_ID_DD_TESTER_LOGIN_REQUEST = 0xA000 + MSG_ID_DD_SOFTWARE_RESET_REQUEST = 0xA001 + MSG_ID_DD_SEND_TEST_CONFIGURATION = 0xA002 + MSG_ID_DD_VALVE_PUBLISH_INTERVAL_OVERRIDE_REQUEST = 0xA003 + MSG_ID_DD_VALVE_STATE_OVERRIDE_REQUEST = 0xA004 + MSG_ID_DD_VALVE_SENSED_STATE_OVERRIDE_REQUEST = 0xA005 + MSG_ID_DD_PRESSURE_SENSOR_READINGS_OVERRIDE_REQUEST = 0xA006 + MSG_ID_DD_PRESSURE_SENSOR_TEMPERATURE_OVERRIDE_REQUEST = 0xA007 + MSG_ID_DD_PRESSURE_SENSOR_READ_COUNTER_OVERRIDE_REQUEST = 0xA008 + MSG_ID_DD_PRESSURE_SENSOR_ERROR_COUNTER_OVERRIDE_REQUEST = 0xA009 + MSG_ID_DD_PRESSURE_SENSOR_PUBLISH_INTERVAL_OVERRIDE_REQUEST = 0xA00A + MSG_ID_DD_PRESSURE_SENSOR_FILTER_READINGS_OVERRIDE_REQUEST = 0xA00B + MSG_ID_DD_PRESSURE_SENSOR_FILTER_TEMPERATURE_OVERRIDE_REQUEST = 0xA00C + MSG_ID_DD_CONDUCTIVITY_SENSOR_READINGS_OVERRIDE_REQUEST = 0xA00D + MSG_ID_DD_CONDUCTIVITY_SENSOR_TEMPERATURE_OVERRIDE_REQUEST = 0xA00E + MSG_ID_DD_CONDUCTIVITY_SENSOR_READ_COUNTER_OVERRIDE_REQUEST = 0xA00F + MSG_ID_DD_CONDUCTIVITY_SENSOR_ERROR_COUNTER_OVERRIDE_REQUEST = 0xA010 + MSG_ID_DD_CONDUCTIVITY_SENSOR_PUBLISH_INTERVAL_OVERRIDE_REQUEST = 0xA011 + MSG_ID_DD_CONCENTRATE_PUMP_PUBLISH_INTERVAL_OVERRIDE_REQUEST = 0xA012 + MSG_ID_DD_CONCENTRATE_PUMP_TARGET_SPEED_OVERRIDE_REQUEST = 0xA013 + MSG_ID_DD_CONCENTRATE_PUMP_MEASURED_SPEED_OVERRIDE_REQUEST = 0xA014 + MSG_ID_DD_CONCENTRATE_PUMP_PARKED_OVERRIDE_REQUEST = 0xA015 + MSG_ID_DD_CONCENTRATE_PUMP_PARK_FAULT_OVERRIDE_REQUEST = 0xA016 + MSG_ID_DD_CONCENTRATE_PUMP_PARK_REQUEST_OVERRIDE_REQUEST = 0xA017 + MSG_ID_DD_TEMPERATURE_SENSOR_PUBLISH_INTERVAL_OVERRIDE_REQUEST = 0xA018 + MSG_ID_DD_TEMPERATURE_SENSOR_MEASURED_TEMPERATURE_OVERRIDE_REQUEST = 0xA019 + MSG_ID_DD_TEMPERATURE_SENSOR_READ_COUNTER_OVERRIDE_REQUEST = 0xA01A + MSG_ID_DD_TEMPERATURE_SENSOR_BARO_READ_COUNTER_OVERRIDE_REQUEST = 0xA01B + MSG_ID_DD_TEMPERATURE_SENSOR_BARO_CRC_OVERRIDE_REQUEST = 0xA01C + MSG_ID_DD_DIALYSATE_PUMPS_PUBLISH_INTERVAL_OVERRIDE_REQUEST = 0xA01D + MSG_ID_DD_DIALYSATE_PUMPS_TARGET_SPEED_OVERRIDE_REQUEST = 0xA01E + MSG_ID_DD_DIALYSATE_PUMPS_MEASURED_SPEED_OVERRIDE_REQUEST = 0xA01F + MSG_ID_DD_DIALYSATE_PUMPS_TARGET_PRESSURE_OVERRIDE_REQUEST = 0xA020 + MSG_ID_DD_DIALYSATE_PUMPS_MEASURED_CURRENT_OVERRIDE_REQUEST = 0xA021 + MSG_ID_DD_DIALYSATE_PUMPS_MEASURED_DIRECTION_OVERRIDE_REQUEST = 0xA022 + MSG_ID_DD_HEATERS_PUBLISH_INTERVAL_OVERRIDE_REQUEST = 0xA023 + MSG_ID_DD_HEATERS_DUTY_CYCLE_OVERRIDE_REQUEST = 0xA024 + MSG_ID_DD_LEVELS_PUBLISH_INTERVAL_OVERRIDE_REQUEST = 0xA025 + MSG_ID_DD_LEVELS_STATUS_OVERRIDE_REQUEST = 0xA026 + MSD_ID_DD_TD_COMMUNICATION_STATUS_OVERRIDE_REQUEST = 0xA027 + MSG_ID_DD_OP_MODE_STATUS_OVERRIDE_REQUEST = 0xA028 + MSG_ID_DD_SET_OPERATION_MODE_OVERRIDE_REQUEST = 0xA029 + MSG_ID_DD_START_GEN_DIALYSATE_MODE_OVERRIDE_REQUEST = 0xA02A + MSG_ID_DD_DIALYSATE_PUMPS_START_STOP_OVERRIDE_REQUEST = 0xA02B + MSG_ID_DD_GEND_MODE_DATA_PUBLISH_OVERRIDE_REQUEST = 0xA02C + MSG_ID_DD_CONCENTRATE_PUMPS_START_STOP_OVERRIDE_REQUEST = 0xA02D + MSG_ID_DD_HEATERS_START_STOP_OVERRIDE_REQUEST = 0xA02E + MSG_ID_DD_VALVES_OPEN_CLOSE_STATE_OVERRIDE_REQUEST = 0xA02F + + MSG_ID_TD_DEBUG_EVENT = 0xFFF1 + MSG_ID_DD_DEBUG_EVENT = 0xFFF2 + + MSG_ID_ACK_MESSAGE_THAT_REQUIRES_ACK = 0xFFFF \ No newline at end of file Index: leahi_dialin/common/td_defs.py =================================================================== diff -u --- leahi_dialin/common/td_defs.py (revision 0) +++ leahi_dialin/common/td_defs.py (revision 2138d06d100fdcf23f2e9069f35ee2fdee62008f) @@ -0,0 +1,433 @@ +########################################################################### +# +# 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 td_defs.py +# +# @author (last) Vinayakam Mani +# @date (last) 10-May-2024 +# @author (original) Peter Lucia +# @date (original) 04-Dec-2020 +# +############################################################################ +from enum import unique +from ..utils.base import DialinEnum + + +@unique +class TDOpModes(DialinEnum): + MODE_FAUL = 0 # Fault mode + MODE_SERV = 1 # Service mode + MODE_INIT = 2 # Initialization & POST mode + MODE_STAN = 3 # Standby mode + MODE_TPAR = 4 # Treatment Parameters mode + MODE_PRET = 5 # Pre-Treatment mode + MODE_TREA = 6 # Treatment mode + MODE_POST = 7 # Post-Treatment mode + MODE_NLEG = 8 # Not legal - an illegal mode transition occurred + NUM_OF_MODES = 9 # Number of TD operation modes + + +@unique +class TDStandbyStates(DialinEnum): + STANDBY_START_STATE = 0 # Start standby (home actuators). + STANDBY_WAIT_FOR_TREATMENT_STATE = 1 # Wait for treatment. + STANDBY_WAIT_FOR_DISINFECT_STATE = 2 # Wait for UI to send disinfect option. + STANDBY_WAIT_FOR_DG_CLEANING_MODE_CMD_RESPONSE_STATE = 3 # Wait for DG cleaning mode command response state. + STANDBY_WAIT_FOR_DG_CLEANING_MODE_TO_START_STATE = 4 # Wait for DG cleaning mode to start state. + STANDBY_CLEANING_MODE_IN_PROGRESS_STATE = 5 # Cleaning mode in progress state. + NUM_OF_STANDBY_STATES = 6 # Number of standby states (sub-modes). + +@unique +class TDInitStates(DialinEnum): + POST_STATE_START = 0 # Start initialize & POST mode state + POST_STATE_FW_INTEGRITY = 1 # Run firmware integrity test state + POST_STATE_RTC = 2 # Run RTC test state + POST_STATE_NVDATAMGMT = 3 # Run NV Data Mgmt. test state + POST_STATE_WATCHDOG = 4 # Run watchdog test state + POST_STATE_SAFETY_SHUTDOWN = 5 # Run safety shutdown test state + POST_STATE_BLOOD_FLOW = 6 # Run blood flow test state + POST_STATE_DIALYSATE_INLET_FLOW = 7 # Run dialysate inlet flow test state + POST_STATE_DIALYSATE_OUTLET_FLOW = 8 # Run dialysate outlet flow test state + POST_STATE_BLOOD_LEAK = 9 # Run blood leak sensor test state + POST_STATE_VALVES = 10 # Run valves test state + POST_STATE_SYRINGE_PUMP = 11 # Run syringe pump test state + POST_STATE_PRES_OCCL = 12 # Run pressure occlusion state + POST_STATE_ALARM_AUDIO = 13 # Run alarm audio test state + POST_STATE_ALARM_LAMP = 14 # Run alarm lamp test state + POST_STATE_ACCELEROMETER = 15 # Run Accelerometer test state + POST_STATE_TEMPERATURES = 16 # Run temperatures POST state + POST_STATE_FANS = 17 # Run fans POST state + POST_STATE_STUCK_BUTTON = 18 # Run stuck button test state + POST_STATE_UI_POST = 19 # Check whether UI passed its POST tests + POST_STATE_FW_COMPATIBILITY = 20 # Run firmware compatibility test state + POST_STATE_FPGA = 21 # Run FPGA test state + POST_STATE_COMPLETED = 22 # POST self-tests completed state + POST_STATE_FAILED = 23 # POST self-tests failed state + NUM_OF_POST_STATES = 24 # Number of initialize & POST mode states + +@unique +class PreTreatmentSubModes(DialinEnum): + TD_PRE_TREATMENT_WATER_SAMPLE_STATE = 0 # Water sample state + TD_PRE_TREATMENT_SELF_TEST_CONSUMABLE_STATE = 1 # Consumable self-tests state + TD_PRE_TREATMENT_SELF_TEST_NO_CART_STATE = 2 # No cartridge self-tests state + TD_PRE_TREATMENT_CART_INSTALL_STATE = 3 # Consumable and cartridge installation state + TD_PRE_TREATMENT_SELF_TEST_DRY_STATE = 4 # Self-tests when the cartridge is dry state + TD_PRE_TREATMENT_PRIME_STATE = 5 # Prime blood and dialysate circuits and run wet self-tests state + TD_PRE_TREATMENT_RECIRCULATE_STATE = 6 # Re-circulate blood and dialysate circuits state + TD_PRE_TREATMENT_PATIENT_CONNECTION_STATE = 7 # Patient connection state + NUM_OF_TD_PRE_TREATMENT_STATES = 8 # Number of pre-treatment mode states + + +@unique +class PreTreatmentSampleWaterStates(DialinEnum): + SAMPLE_WATER_SETUP_STATE = 0 # Sample water setup (flush filter) state + SAMPLE_WATER_STATE = 1 # Sample water state, receiving sample water commands from the user + SAMPLE_WATER_COMPLETE_STATE = 2 # Sample water complete state + NUM_OF_SAMPLE_WATER_STATES = 3 # Number of sample water sub-mode states + + +@unique +class PreTreatmentConsumableSelfTestStates(DialinEnum): + CONSUMABLE_SELF_TESTS_INSTALL_STATE = 0 # Consumable self-tests install state + CONSUMABLE_SELF_TESTS_PRIME_STATE = 1 # Consumable self-tests prime concentrate lines state + CONSUMABLE_SELF_TESTS_BICARB_PUMP_CHECK_STATE = 2 # Consumable self-tests bicarbonate concentrate pump check state + CONSUMABLE_SELF_TESTS_ACID_PUMP_CHECK_STATE = 3 # Consumable self-tests acid concentrate pump check state + CONSUMABLE_SELF_TESTS_COMPLETE_STATE = 4 # Consumable self-tests complete state + NUM_OF_CONSUMABLE_SELF_TESTS_STATES = 5 # Number of consumable install sub-mode states + + +@unique +class PreTreatmentNoCartSelfTestStates(DialinEnum): + NO_CART_SELF_TESTS_WAIT_FOR_DOOR_CLOSE_STATE = 0 # Wait for door to be closed before running self-tests + NO_CART_SELF_TESTS_PRESSURE_CHECKS_STATE = 1 # No cartridge pressure sensors self-test state + NO_CART_SELF_TESTS_HOME_VALVES_STATE = 2 # No cartridge home valves state + NO_CART_SELF_TESTS_HOME_SYRINGE_PUMP_STATE = 3 # No cartridge home syringe pump state + NO_CART_SELF_TESTS_PUMPS_STATE = 4 # No cartridge self-test for blood pump, dialysate in pump, dialysate out pump state + NO_CART_SELF_TESTS_HOME_IDLE_STATE = 5 # Wait for valves and pumps finish homing state + NO_CART_SELF_TESTS_STOPPED_STATE = 6 # No cart self-test stopped state + NO_CART_SELF_TESTS_COMPLETE_STATE = 7 # No cartridge self-test complete state + NUM_OF_NO_CART_SELF_TESTS_STATES = 8 # Number of no cartridge self-tests states + + +@unique +class PreTreatmentWetSelfTestStates(DialinEnum): + WET_SELF_TESTS_START_STATE = 0 + WET_SELF_TESTS_BUBBLE_CHECK_SETUP_STATE = 1 + WET_SELF_TESTS_BUBBLE_CHECK_STATE = 2 + WET_SELF_TESTS_PRIME_CHECK_STATE = 3 + WET_SELF_TESTS_BLOOD_LEAK_DETECTOR_DEBUBBLE_STATE = 4 + WET_SELF_TESTS_BLOOD_LEAK_DETECTOR_STATE = 5 + WET_SELF_TESTS_FIRST_DISPLACEMENT_SETUP_STATE = 6 + WET_SELF_TESTS_FIRST_DISPLACEMENT_STATE = 7 + WET_SELF_TESTS_FIRST_DISPLACEMENT_VERIFY_STATE = 8 + WET_SELF_TESTS_SECOND_DISPLACEMENT_SETUP_STATE = 9 + WET_SELF_TESTS_SECOND_DISPLACEMENT_STATE = 10 + WET_SELF_TESTS_SECOND_DISPLACEMENT_VERIFY_STATE = 11 + WET_SELF_TESTS_STOPPED_STATE = 12 + WET_SELF_TESTS_COMPLETE_STATE = 13 + +@unique +class TDPreTreatmentReservoirMgmtStates(DialinEnum): + PRE_TREATMENT_RESERVOIR_MGMT_START_STATE = 0 # Wait for signal to start drain and fill reservoirs + PRE_TREATMENT_RESERVOIR_MGMT_DRAIN_CMD_STATE = 1 # Command DG to start draining reservoir + PRE_TREATMENT_RESERVOIR_MGMT_DRAIN_CMD_RESP_STATE = 2 # After sending drain command, process DG drain command response + PRE_TREATMENT_RESERVOIR_MGMT_START_FILL_STATE = 3 # Command DG to start filling reservoir + PRE_TREATMENT_RESERVOIR_MGMT_FILL_CMD_RESP_STATE = 4 # After sending fill command, process DG fill command response + PRE_TREATMENT_RESERVOIR_MGMT_FILL_COMPLETE_STATE = 5 # Reservoir fill has completed + PRE_TREATMENT_RESERVOIR_MGMT_REQUEST_RESERVOIR_SWITCH_STATE = 6 # Command DG to switch (toggle) reservoirs + PRE_TREATMENT_RESERVOIR_MGMT_WAIT_FOR_RESERVOIR_SWITCH_STATE = 7 # After sending switch command, process DG fill command response + PRE_TREATMENT_RESERVOIR_MGMT_COMPLETE_STATE = 8 # Pre-treatment reservoir management complete state + NUM_OF_PRE_TREATMENT_RESERVOIR_MGMT_STATES = 9 # Number of pre-treatments reservoir mgmt. states + +# Heparin states +@unique +class HeparinStates(DialinEnum): + HEPARIN_STATE_OFF = 0 # No heparin delivery is in progress + HEPARIN_STATE_STOPPED = 1 # Heparin delivery stopped by alarm or not yet started + HEPARIN_STATE_PAUSED = 2 # Heparin delivery paused + HEPARIN_STATE_INITIAL_BOLUS = 3 # Initial heparin bolus delivery in progress + HEPARIN_STATE_DISPENSING = 4 # Gradual heparin dispensing in progress + HEPARIN_STATE_COMPLETED = 5 # Heparin delivery stopped due to the set stop time before treatment end + HEPARIN_STATE_EMPTY = 6 # Heparin Syringe empty + NUM_OF_HEPARIN_STATES = 7 # Number of saline bolus states + + +# Syringe pump states +@unique +class SyringePumpStates(DialinEnum): + SYRINGE_PUMP_INIT_STATE = 0 # Syringe pump initialize state + SYRINGE_PUMP_OFF_STATE = 1 # Syringe pump off state + SYRINGE_PUMP_RETRACT_STATE = 2 # Syringe pump retract state + SYRINGE_PUMP_PRELOAD_STATE = 3 # Syringe pump preload state + SYRINGE_PUMP_SEEK_STATE = 4 # Syringe pump seek state + SYRINGE_PUMP_PRIME_STATE = 5 # Syringe pump prime state + SYRINGE_PUMP_HEP_BOLUS_STATE = 6 # Syringe pump bolus state + SYRINGE_PUMP_HEP_CONTINUOUS_STATE = 7 # Syringe pump continuous state + SYRINGE_PUMP_CONFIG_FORCE_SENSOR_STATE = 8 # Syringe pump configure force sensor state + + +# Syringe pump operations +@unique +class SyringePumpOperations(DialinEnum): + SYRINGE_PUMP_OP_STOP = 0 # Stop syringe pump + SYRINGE_PUMP_OP_RETRACT = 1 # Retract syringe pump + SYRINGE_PUMP_OP_SEEK = 2 # Seek plunger + SYRINGE_PUMP_OP_PRIME = 3 # Prime Heparin line + SYRINGE_PUMP_OP_BOLUS = 4 # Deliver Heparin bolus of set volume over 5 minutes + SYRINGE_PUMP_OP_CONTINUOUS = 5 # Continuous dispense of Heparin at set rate + + +@unique +class PostTreatmentStates(DialinEnum): + TD_POST_TREATMENT_DRAIN_RESERVOIRS_STATE = 0 # Drain reservoirs state + TD_POST_TREATMENT_PATIENT_DISCONNECTION_STATE = 1 # Patient disconnection state + TD_POST_TREATMENT_DISPOSABLE_REMOVAL_STATE = 2 # Disposable removal state + TD_POST_TREATMENT_VERIFY_STATE = 3 # Verify cartridge removed, syringe removed, and reservoirs drained state + NUM_OF_TD_POST_TREATMENT_STATES = 4 # Number of post-treatment mode states + + +@unique +class PreTreatmentCartridgeInstallStates(DialinEnum): + CARTRIDGE_INSTALL_STATE = 0 # Pre-treatment Cartridge Install state. + NUM_OF_CARTRIDGE_INSTALL_STATES = 1 # Number of pre-treatment Cartridge Install states. + + +@unique +class PreTreatmentDrySelfTestsStates(DialinEnum): + """ + The HD Pre-Treatment dry self test states + """ + DRY_SELF_TESTS_START_STATE = 0 # Dry self-tests starting state + DRY_SELF_TESTS_WAIT_FOR_DOOR_CLOSE_STATE = 1 # Wait for door to close before executing self-tests + DRY_SELF_TESTS_USED_CARTRIDGE_CHECK_STATE = 2 # Used cartridge check dry self-test state + DRY_SELF_TESTS_CARTRIDGE_LOADED_CHECK_STATE = 3 # Cartridge loaded check dry self-test state + DRY_SELF_TESTS_SYRINGE_PUMP_SEEK_STATE = 4 # Seek syringe pumps state + DRY_SELF_TESTS_PRESSURE_SENSORS_NORMAL_SETUP_STATE = 5 # Pressure sensor setup state. + DRY_SELF_TESTS_PRESSURE_SENSORS_VENOUS_SETUP_STATE = 6 # Venous pressure sensor dry self-test setup valves and pump state + DRY_SELF_TESTS_PRESSURE_SENSORS_VENOUS = 7 # Venous pressure sensor dry self-test + DRY_SELF_TESTS_PRESSURE_SENSORS_ARTERIAL_SETUP_STATE = 8 # Arterial pressure sensor dry self-test setup valves and pump state + DRY_SELF_TESTS_PRESSURE_SENSORS_ARTERIAL = 9 # Arterial pressure sensor dry self-test + DRY_SELF_TESTS_PRESSURE_SENSORS_DECAY_STATE = 10 # Pressure sensors verify pressure loss state + DRY_SELF_TESTS_PRESSURE_SENSORS_STABILITY_STATE = 11 # Pressure sensors verify pressure stability state + DRY_SELF_TESTS_PRESSURE_SENSORS_NORMAL_STATE = 12 # Pressure sensors verify normal pressure readings state + DRY_SELF_TESTS_SYRINGE_PUMP_PRIME_STATE = 13 # Prime syringe pump state + DRY_SELF_TESTS_SYRINGE_PUMP_OCCLUSION_DETECTION_STATE = 14 # Occlusion detection state + DRY_SELF_TESTS_STOPPED_STATE = 15 # Dry self-test stopped state + DRY_SELF_TESTS_COMPLETE_STATE = 16 # Dry self-test complete state + NUM_OF_DRY_SELF_TESTS_STATES = 17 # Number of dry self-tests states + + +@unique +class PreTreatmentPrimeStates(DialinEnum): + TD_PRIME_WAIT_FOR_USER_START_STATE = 0 # Wait for user to start prime state + TD_PRIME_SALINE_SETUP_STATE = 1 # Saline setup state + TD_PRIME_SALINE_PURGE_AIR_STATE = 2 # Saline purge air state + TD_PRIME_SALINE_CIRC_BLOOD_CIRCUIT_STATE = 3 # Circulate blood circuit state + TD_PRIME_RESERVOIR_ONE_FILL_COMPLETE_STATE = 4 # Wait for reservoir 1 fill complete + TD_PRIME_DIALYSATE_DIALYZER_STATE = 5 # Dialysate dialyzer fluid path state + TD_PRIME_SALINE_DIALYZER_SETUP_STATE = 6 # Saline dialyzer setup state + TD_PRIME_SALINE_DIALYZER_STATE = 7 # Saline dialyzer fluid path state + TD_PRIME_RESERVOIR_TWO_FILL_COMPLETE_STATE = 8 # Wait for reservoir 2 fill complete + TD_PRIME_DIALYSATE_BYPASS_STATE = 9 # Dialysate bypass fluid path state + TD_PRIME_WET_SELF_TESTS_STATE = 10 # Perform wet self-tests after priming complete + TD_PRIME_PAUSE = 11 # Prime pause state, waits to be resumed + TD_PRIME_COMPLETE = 12 # Prime complete state + NUM_OF_TD_PRIME_STATES = 13 # Number of prime sub-mode states + + +@unique +class PreTreatmentRecircStates(DialinEnum): + PRE_TREATMENT_RECIRC_STATE = 0 # Pre-treatment recirculate state + PRE_TREATMENT_RECIRC_STOPPED_STATE = 1 # Pre-treatment recirculate stopped state + NUM_OF_PRE_TREATMENT_RECIRC_STATES = 2 # Number of pre-treatment recirculate states + + +@unique +class TreatmentRecircStates(DialinEnum): + TREATMENT_RECIRC_DISCONNECT_PATIENT_STATE = 0 # Disconnect patient state of the treatment re-circulate sub-mode state machine + TREATMENT_RECIRC_RECIRC_STATE = 1 # Re-circulate Dialysate state of the treatment re-circulate sub-mode state machine + TREATMENT_RECIRC_STOPPED_STATE = 2 # Stopped state of the treatment re-circulate sub-mode state machine + TREATMENT_RECIRC_RECONNECT_PATIENT_STATE = 3 # Reconnect patient state of the treatment re-circulate sub-mode state machine + NUM_OF_TREATMENT_RECIRC_STATES = 4 # Number of treatment re-circulate sub-mode states + + +@unique +class PreTreatmentPatientConnectionStates(DialinEnum): + PRE_TREATMENT_PAT_CONN_WAIT_FOR_UF_VOL_STATE = 0 # Pre-treatment patient connect wait for UF volume setting state + PRE_TREATMENT_PAT_CONN_WAIT_FOR_DLZR_INVERT_STATE = 1 # Pre-treatment patient connect wait for dialyzer inverted state + PRE_TREATMENT_PAT_CONN_WAIT_FOR_USER_CONFIRM_STATE = 2 # Pre-treatment patient connect wait for user confirm state + PRE_TREATMENT_PAT_CONN_WAIT_FOR_TREATMENT_START_STATE = 3 # Pre-treatment patient connect wait for treatment start state + NUM_OF_PRE_TREATMENT_PAT_CONN_STATES = 4 # Number of pre-treatment patient connect states + + +@unique +class TreatmentParametersStates(DialinEnum): + TD_TREATMENT_PARAMS_MODE_STATE_WAIT_4_UI_2_SEND = 0 # Wait for UI to send treatment params mode state + TD_TREATMENT_PARAMS_MODE_STATE_WAIT_4_UI_2_CONFIRM = 1 # Wait for UI to confirm treatment params mode state + NUM_OF_TD_TREATMENT_PARAMS_MODE_STATES = 2 # Number of treatment params mode states + + +@unique +class TreatmentStates(DialinEnum): + TREATMENT_START_STATE = 0 # Start treatment - initialize treatment and go to blood prime state + TREATMENT_BLOOD_PRIME_STATE = 1 # Prime blood-side of dialyzer with gradual ramp for 1 min. while dialyzer is bypassed. No dialysis or UF taking place. No treatment time. + TREATMENT_DIALYSIS_STATE = 2 # Perform dialysis. Deliver Heparin as prescribed. Deliver UF as prescribed. Handle saline boluses as requested + TREATMENT_STOP_STATE = 3 # Treatment stopped. All pumps off. Dializer bypassed + TREATMENT_RINSEBACK_STATE = 4 # Perform rinseback with saline. Dialyzer bypassed. Dialysate recirculating + TREATMENT_RECIRC_STATE = 5 # Recirculate saline and dialysate while patient disconnected. Blood lines open and shunted. Dialyzer is bypassed + TREATMENT_END_STATE = 6 # Dialysis has ended. Blood pump slowed. Dialyzer is bypassed. Dialysate is recirculated. User can rinseback + NUM_OF_TREATMENT_STATES = 7 # Number of treatment states (sub-modes) + +@unique +class TreatmentBloodPrimeStates(DialinEnum): + BLOOD_PRIME_RAMP_STATE = 0 # Ramp state of the blood prime sub-mode state machine + NUM_OF_BLOOD_PRIME_STATES = 1 # Number of blood prime sub-mode states + +@unique +class TreatmentDialysisStates(DialinEnum): + DIALYSIS_START_STATE = 0 # Start state of dialysis sub-mode state machine + DIALYSIS_UF_STATE = 1 # Ultrafiltration state of the dialysis sub-mode state machine + DIALYSIS_SALINE_BOLUS_STATE = 2 # Saline bolus state of the dialysis sub-mode state machine + NUM_OF_DIALYSIS_STATES = 3 # Number of dialysis sub-mode states + +@unique +class TreatmentStopStates(DialinEnum): + TREATMENT_STOP_RECIRC_STATE = 0 # Dialysate and Blood re-circulation state of the treatment stop sub-mode state machine + TREATMENT_STOP_RECIRC_DIALYSATE_ONLY_STATE = 1 # Re-circulate Dialysate only state of the treatment re-circulate sub-mode state machine + TREATMENT_STOP_RECIRC_BLOOD_ONLY_STATE = 2 # Re-circulate Blood only state of the treatment re-circulate sub-mode state machine + TREATMENT_STOP_NO_RECIRC_STATE = 3 # No re-circulation state of the treatment stop sub-mode state machine + NUM_OF_TREATMENT_STOP_STATES = 4 # Number of treatment stop sub-mode states + +@unique +class TreatmentRinsebackStates(DialinEnum): + RINSEBACK_STOP_INIT_STATE = 0 # Start state (stopped) of the rinseback sub-mode state machine + RINSEBACK_RUN_STATE = 1 # Rinseback running state of the rinseback sub-mode state machine + RINSEBACK_PAUSED_STATE = 2 # Rinseback paused state of the rinseback sub-mode state machine + RINSEBACK_STOP_STATE = 3 # Rinseback stopped (done) state of the rinseback sub-mode state machine + RINSEBACK_RUN_ADDITIONAL_STATE = 4 # Additional rinseback volume (10 mL) state of the rinseback sub-mode state machine + RINSEBACK_RECONNECT_PATIENT_STATE = 5 # Rinseback reconnect patient state of the rinseback sub-mode state machine + NUM_OF_RINSEBACK_STATES = 6 # Number of rinseback sub-mode states + +@unique +class TreatmentRecircStates(DialinEnum): + TREATMENT_RECIRC_DISCONNECT_PATIENT_STATE = 0 # Disconnect patient state of the treatment re-circulate sub-mode state machine + TREATMENT_RECIRC_RECIRC_STATE = 1 # Re-circulate Dialysate state of the treatment re-circulate sub-mode state machine + TREATMENT_RECIRC_STOPPED_STATE = 2 # Stopped state of the treatment re-circulate sub-mode state machine + TREATMENT_RECIRC_RECONNECT_PATIENT_STATE = 3 # Reconnect patient state of the treatment re-circulate sub-mode state machine + NUM_OF_TREATMENT_RECIRC_STATES = 4 # Number of treatment re-circulate sub-mode states + +@unique +class TreatmentEndStates(DialinEnum): + TREATMENT_END_WAIT_FOR_RINSEBACK_STATE = 0 # Wait for rinseback state of the treatment end sub-mode state machine + TREATMENT_END_PAUSED_STATE = 1 # Paused state of the treatment end sub-mode state machine + NUM_OF_TREATMENT_END_STATES = 2 # Number of treatment end sub-mode states + +@unique +class TDFaultStates(DialinEnum): + TD_FAULT_STATE_START = 0 # Start fault state + TD_FAULT_STATE_RUN_NV_POSTS = 1 # TD fault run NV posts state + TD_FAULT_STATE_COMPLETE = 2 # TD fault run complete state + NUM_OF_TD_FAULT_STATES = 3 # Number of fault mode states + +@unique +class TDEventList(DialinEnum): + TD_EVENT_STARTUP = 0 # TD startup event + TD_EVENT_OP_MODE_CHANGE = 1 # TD Op mode change event + TD_EVENT_SUB_MODE_CHANGE = 2 # TD Op sub-mode change event + TD_EVENT_DRY_SELF_TEST_CARTRIDGE_RESULT = 3 # TD dry self test cartridge result + TD_EVENT_DRY_SELF_TEST_PRESSURE_RESULT = 4 # TD dry self test pressure result + TD_EVENT_WET_SELF_TEST_DISPLACEMENT_RESULT = 5 # TD wet self test displacement result + TD_EVENT_CPU_RAM_ERROR_STATUS = 6 # TD CPU RAM error status + TD_EVENT_CAL_RECORD_UPDATE = 7 # TD new calibration record updated + TD_EVENT_SYSTEM_RECORD_UPDATE = 8 # TD new system record has been updated + TD_EVENT_SERVICE_UPDATE = 9 # TD new service record has been updated + TD_EVENT_USAGE_INFO_UPDATE = 10 # TD new usage information has been updated + TD_EVENT_SW_CONFIG_UPDATE = 11 # TD new software configuration has been updated + TD_EVENT_BUTTON = 12 # TD button pressed/released + TD_EVENT_SAFETY_LINE = 13 # TD safety line pulled/released + TD_EVENT_RSRVR_1_LOAD_CELL_START_VALUES = 14 # TD reservoir 1 load cells start values + TD_EVENT_RSRVR_1_LOAD_CELL_END_VALUES = 15 # TD reservoir 2 load cells end values + TD_EVENT_RSRVR_2_LOAD_CELL_START_VALUES = 16 # TD reservoir 2 load cells start values + TD_EVENT_RSRVR_2_LOAD_CELL_END_VALUES = 17 # TD reservoir 2 load cells end values + TD_EVENT_SUB_STATE_CHANGE = 18 # TD Op sub-state change event + TD_EVENT_SYRINGE_PUMP_STATE = 19 # TD syringe pump state change event + TD_EVENT_OCCLUSION_BASELINE = 20 # TD event occlusion baseline event + TD_EVENT_RSRVR_UF_VOLUME_AND_TIME = 21 # TD ultrafiltration volume and time for a reservoir use + TD_EVENT_RSRVR_UF_RATE = 22 # TD ultrafiltration measured and expected rates + TD_EVENT_OPERATION_STATUS = 23 # TD operation status event. + TD_EVENT_AIR_TRAP_FILL = 24 # TD initiated an air trap fill (opened VBT briefly). + TD_EVENT_AIR_PUMP_ON_OFF = 25 # TD turned air pump on or off. + TD_EVENT_BLOOD_LEAK_SELF_TEST_RESULT = 26 # TD Blood leak self test result. + TD_EVENT_BLOOD_LEAK_NUM_OF_SET_POINT_CHECK_FAILURES = 27 # TD blood leak number of setpoint check failures + TD_EVENT_DRY_SELF_TEST_PRESSURE_DECAY_WAIT_PERIOD = 28 # TD dry self test pressure decay wait period + TD_EVENT_INSTIT_RECORD_UPDATE = 29 # TD new institutional record has been updated. + TD_EVENT_PARTIAL_OCCLUSION_BASELINE = 30 # TD event partial occlusion baseline event + NUM_OF_EVENT_IDS = 31 # Total number of TD events + +@unique +class TDEventDataType(DialinEnum): + EVENT_DATA_TYPE_NONE = 0 + EVENT_DATA_TYPE_U32 = 1 + EVENT_DATA_TYPE_S32 = 2 + EVENT_DATA_TYPE_F32 = 3 + EVENT_DATA_TYPE_BOOL = 4 + NUM_OF_EVENT_DATA_TYPES = 5 + +@unique +class UFStates(DialinEnum): + UF_PAUSED_STATE = 0 # Paused state of the ultrafiltration state machine + UF_RUNNING_STATE = 1 # Running state of the ultrafiltration state machine + NUM_OF_UF_STATES = 2 # Number of ultrafiltration states + +@unique +class SalineBolusStates(DialinEnum): + SALINE_BOLUS_STATE_IDLE = 0 # No saline bolus delivery is in progress + SALINE_BOLUS_STATE_WAIT_FOR_PUMPS_STOP = 1 # Wait for pumps to stop before starting bolus + SALINE_BOLUS_STATE_IN_PROGRESS = 2 # A saline bolus delivery is in progress + SALINE_BOLUS_STATE_MAX_DELIVERED = 3 # Maximum saline bolus volume reached - no more saline bolus deliveries allowed + NUM_OF_SALINE_BOLUS_STATES = 4 # Number of saline bolus states + +@unique +class TreatmentParameters(DialinEnum): + TREATMENT_PARAM_BLOOD_FLOW_RATE_ML_MIN = 0 + TREATMENT_PARAM_DIALYSATE_FLOW_RATE_ML_MIN = 1 + TREATMENT_PARAM_TREATMENT_DURATION_MIN = 2 + TREATMENT_PARAM_HEPARIN_PRESTOP_MIN = 3 + TREATMENT_PARAM_SALINE_BOLUS_VOLUME_ML = 4 + TREATMENT_PARAM_ACID_CONCENTRATE = 5 + TREATMENT_PARAM_BICARB_CONCENTRATE = 6 + TREATMENT_PARAM_DIALYZER_TYPE = 7 + TREATMENT_PARAM_HEPARIN_TYPE = 8 + TREATMENT_PARAM_BLOOD_PRESSURE_MEAS_INTERVAL_MIN = 9 + TREATMENT_PARAM_RINSEBACK_FLOW_RATE_ML_MIN = 10 + TREATMENT_PARAM_ART_PRES_LIMIT_WINDOW = 11 + TREATMENT_PARAM_VEN_PRES_LIMIT_WINDOW = 12 + TREATMENT_PARAM_VEN_PRES_LIMIT_ASYMMETRIC = 13 + TREATMENT_PARAM_HEPARIN_DISPENSE_RATE_ML_HR = 14 + TREATMENT_PARAM_HEPARIN_BOLUS_VOLUME_ML = 15 + TREATMENT_PARAM_DIALYSATE_TEMPERATURE_C = 16 + TREATMENT_PARAM_UF_VOLUME_L = 17 + NUM_OF_TREATMENT_PARAMS = 18 + +class Acid_Concentrates(DialinEnum): + ACID_CONC_TYPE_FRESENIUS_08_1251_1 = 0 + ACID_CONC_TYPE_FRESENIUS_08_2251_0 = 1 + ACID_CONC_TYPE_FRESENIUS_08_3251_9 = 2 + NUM_OF_ACID_CONC_TYPES = 3 + +class Bicarb_Concentrates(DialinEnum): + BICARB_CONC_TYPE_FRESENIUS_CENTRISOL = 0 + NUM_OF_BICARB_CONC_TYPES = 1 + +@unique +class PowerOffCommands(DialinEnum): + """ + power off commands enum + """ + PW_COMMAND_OPEN = 0 + PW_TIMEOUT_CLOSE = 1 + PW_REJECT_SHOW = 2 Index: leahi_dialin/common/test_config_defs.py =================================================================== diff -u --- leahi_dialin/common/test_config_defs.py (revision 0) +++ leahi_dialin/common/test_config_defs.py (revision 2138d06d100fdcf23f2e9069f35ee2fdee62008f) @@ -0,0 +1,40 @@ +########################################################################### +# +# Copyright (c) 2023-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 test_config_defs.py +# +# @author (last) Dara Navaei +# @date (last) 16-Aug-2023 +# @author (original) Dara Navaei +# @date (original) 24-Apr-2023 +# +############################################################################ + +from enum import unique +from ..utils.base import DialinEnum + + +@unique +class DGTestConfigOptions(DialinEnum): + TEST_CONFIG_MIX_WITH_WATER = 0 # Test config mix with water. + TEST_CONFIG_DISABLE_INLET_WATER_TEMP_CHECK = 1 # Test config disable inlet water temperature check + TEST_CONFIG_RECOVER_TREATMENT = 2 # Test config recover treatment + NUM_OF_TEST_CONFIGS = 3 # Number of test configuration. + + +@unique +class HDTestConfigOptions(DialinEnum): + TEST_CONFIG_USE_WET_CARTRIDGE = 0 # Test configuration use wet cartridge. + TEST_CONFIG_USE_WORN_CARTRIDGE = 1 # Test configuration use worn cartridge. + TEST_CONFIG_EXPEDITE_PRE_TREATMENT = 2 # Test configuration expedite pre-treatment. + TEST_CONFIG_SKIP_BLOOD_PRIME = 3 # Test configuration skip blood prime. + TEST_CONFIG_SKIP_DISINFECT_AND_SERVICE_TX_BLOCKERS = 4 # Test configuration skip disinfect and service treatment blockers. + TEST_CONFIG_DISABLE_WET_SELFTEST_DISPLACEMENT_CHECK = 5 # Test configuration disable wet self test displacement check. + TEST_CONFIG_RECOVER_TREATMENT = 6 # Test configuration recover treatment. + TEST_CONFIG_ENABLE_ONE_MINUTE_TREATMENT = 7 # Test configuration enable one minute treatment. + TEST_CONFIG_DISABLE_BLOOD_LEAK_ALARM = 8 # Test configuration disable blood leak alarm. + NUM_OF_TEST_CONFIGS = 9 # Number of test configurations Index: leahi_dialin/common/ui_defs.py =================================================================== diff -u --- leahi_dialin/common/ui_defs.py (revision 0) +++ leahi_dialin/common/ui_defs.py (revision 2138d06d100fdcf23f2e9069f35ee2fdee62008f) @@ -0,0 +1,172 @@ +########################################################################### +# +# Copyright (c) 2021-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 ui_defs.py +# +# @author (last) Vy +# @date (last) 10-Oct-2023 +# @author (original) Peter Lucia +# @date (original) 06-Apr-2021 +# +############################################################################ + +from . import RequestRejectReasons + + +class EResponse: + Rejected = 0 + Accepted = 1 + + +class GuiActionType: + Unknown = 0 + PowerOff = 1 + KeepAlive = 7 + BloodFlow = 5 + DialysateInletFlow = 8 + DialysateOutletFlow = 11 + TreatmentTime = 13 + PowerOffBroadcast = 14 + + AlarmStatus = 2 + AlarmTriggered = 3 + AlarmCleared = 4 + + PressureOcclusion = 9 + + DGDrainPumpData = 36 + DGHeatersData = 44 + LoadCellReadings = 12 + DGPressuresData = 32 + TemperatureSensors = 45 + + CanBUSFaultCount = 2457 + HDDebugText = 0xFFF1 + DGDebugText = 0xFFF2 + + AdjustBloodDialysateReq = 23 + AdjustBloodDialysateRsp = 24 + + AdjustDurationReq = 22 + AdjustDurationRsp = 27 + + AdjustUltrafiltrationStateReq = 16 + AdjustUltrafiltrationStateRsp = 65 + AdjustUltrafiltrationEditReq = 17 + AdjustUltrafiltrationEditRsp = 19 + AdjustUltrafiltrationConfirmReq = 21 + AdjustUltrafiltrationConfirmRsp = 46 + + AdjustPressuresLimitsReq = 70 + AdjustPressuresLimitsRsp = 71 + + TreatmentRanges = 26 + + String = 65279 + Acknow = 65535 + # Generic Acknowledgment is not a unique message ID and + # inherits its Id from the actual message. Zero is a placeholder + AcknowGeneric = 0 + + +class GuiActionTypeLength: + TREATMENT_LOG_LENGTH = 33 + + +class TXStates: + # Sub Mode + TREATMENT_START_STATE = 0 # Start treatment - initialize treatment and go to blood prime state + TREATMENT_BLOOD_PRIME_STATE = 1 # Prime blood-side of dialyzer with gradual ramp for 1 min. + TREATMENT_DIALYSIS_STATE = 2 # Perform dialysis. + TREATMENT_STOP_STATE = 3 # Treatment stopped. All pumps off. Dializer bypassed + TREATMENT_RINSEBACK_STATE = 4 # Perform rinseback with saline. Dialyzer bypassed. Dialysate recirculating + TREATMENT_RECIRC_STATE = 5 # Recirculate saline and dialysate while patient disconnected + TREATMENT_END_STATE = 6 # Dialysis has ended + + # Saline states + SALINE_BOLUS_STATE_IDLE = 0 # No saline bolus delivery is in progress + SALINE_BOLUS_STATE_WAIT_FOR_PUMPS_STOP = 1 # Wait for pumps to stop before starting bolus + SALINE_BOLUS_STATE_IN_PROGRESS = 2 # A saline bolus delivery is in progress + SALINE_BOLUS_STATE_MAX_DELIVERED = 3 # Maximum saline bolus volume reached + + # UF states + UF_PAUSED_STATE = 0 # Paused state of the ultrafiltration state machine + UF_RUNNING_STATE = 1 # Running state of the ultrafiltration state machine + UF_OFF_STATE = 3 # Completed/off state of the ultrafiltration state machine + UF_COMPLETED_STATE = 4 # Completed state of ultrafiltration state machine + + # Heparin states + HEPARIN_STATE_OFF = 0 # No heparin delivery is in progress + HEPARIN_STATE_PAUSED = 1 # Heparin delivery paused + HEPARIN_STATE_INITIAL_BOLUS = 2 # Initial heparin bolus delivery in progress + HEPARIN_STATE_DISPENSING = 3 # Gradual heparin dispensing in progress + HEPARIN_STATE_COMPLETED = 4 # Heparin delivery stopped due to the set stop time before treatment end + HEPARIN_STATE_EMPTY = 5 # Heparin Syringe empty + + # Rinseback states + RINSEBACK_STOP_INIT_STATE = 0 # Start state (stopped) of the rinseback sub-mode state machine + RINSEBACK_RUN_STATE = 1 # Rinseback running state of the rinseback sub-mode state machine + RINSEBACK_PAUSED_STATE = 2 # Rinseback paused state of the rinseback sub-mode state machine + RINSEBACK_STOP_STATE = 3 # Rinseback stopped (done) state of the rinseback sub-mode state machine + RINSEBACK_RUN_ADDITIONAL_STATE = 4 # Additional rinseback volume (10 mL) state + + # Recirculate + TREATMENT_RECIRC_RECIRC_STATE = 0 # Re-circulate state of the treatment re-circulate sub-mode state machine + TREATMENT_RECIRC_STOPPED_STATE = 1 # Stopped state of the treatment re-circulate sub-mode state machine + + # Blood Prime + BLOOD_PRIME_RAMP_STATE = 0 # Ramp state of the blood prime sub-mode state machine + + # Treatment End + TREATMENT_END_WAIT_FOR_RINSEBACK_STATE = 0 # Wait for rinseback state of the treatment end sub-mode state machine + TREATMENT_END_PAUSED_STATE = 1 # Paused state of the treatment end sub-mode state machine + + # Treatment Stop + TREATMENT_STOP_RECIRC_STATE = 0 # Dialysate re-circulation state + TREATMENT_STOP_NO_RECIRC_STATE = 1 # No dialysate re-circulation state + + +class TreatmentParameterRejections: + def __init__(self): + self.param_request_valid = RequestRejectReasons.REQUEST_REJECT_REASON_NONE + self.param_blood_flow_rate = RequestRejectReasons.REQUEST_REJECT_REASON_NONE + self.param_dialysate_flow_rate = RequestRejectReasons.REQUEST_REJECT_REASON_NONE + self.param_duration = RequestRejectReasons.REQUEST_REJECT_REASON_NONE + self.param_heparin_stop_time = RequestRejectReasons.REQUEST_REJECT_REASON_NONE + self.param_saline_bolus = RequestRejectReasons.REQUEST_REJECT_REASON_NONE + self.param_acid_concentrate = RequestRejectReasons.REQUEST_REJECT_REASON_NONE + self.param_bicarbonate_concentrate = RequestRejectReasons.REQUEST_REJECT_REASON_NONE + self.param_dialyzer_type = RequestRejectReasons.REQUEST_REJECT_REASON_NONE + self.param_blood_pressure_measure_interval = RequestRejectReasons.REQUEST_REJECT_REASON_NONE + self.param_rinseback_flow_rate = RequestRejectReasons.REQUEST_REJECT_REASON_NONE + self.param_arterial_pressure_limit_low = RequestRejectReasons.REQUEST_REJECT_REASON_NONE + self.param_arterial_pressure_limit_high = RequestRejectReasons.REQUEST_REJECT_REASON_NONE + self.param_venous_pressure_limit_low = RequestRejectReasons.REQUEST_REJECT_REASON_NONE + self.param_venous_pressure_limit_high = RequestRejectReasons.REQUEST_REJECT_REASON_NONE + self.param_heparin_dispensing_rate = RequestRejectReasons.REQUEST_REJECT_REASON_NONE + self.param_heparin_bolus_volume = RequestRejectReasons.REQUEST_REJECT_REASON_NONE + self.param_dialysate_temp = RequestRejectReasons.REQUEST_REJECT_REASON_NONE + + def set_all_valid(self): + """ + Sets all parameters as valid + + @return: None + """ + for attr in dir(self): + if not callable(getattr(self, attr)) and attr.startswith("param_"): + self.__dict__[attr] = RequestRejectReasons.REQUEST_REJECT_REASON_NONE + + def set_all_invalid(self): + """ + Sets all treatment parameters to be invalid + + @return: None + """ + for attr in dir(self): + if not callable(getattr(self, attr)) and attr.startswith("param_"): + self.__dict__[attr] = RequestRejectReasons.REQUEST_REJECT_REASON_NOT_ALLOWED_IN_CURRENT_MODE Index: leahi_dialin/dd/__init__.py =================================================================== diff -u --- leahi_dialin/dd/__init__.py (revision 0) +++ leahi_dialin/dd/__init__.py (revision 2138d06d100fdcf23f2e9069f35ee2fdee62008f) @@ -0,0 +1 @@ \ No newline at end of file Index: leahi_dialin/dd/conductivity_sensors.py =================================================================== diff -u --- leahi_dialin/dd/conductivity_sensors.py (revision 0) +++ leahi_dialin/dd/conductivity_sensors.py (revision 2138d06d100fdcf23f2e9069f35ee2fdee62008f) @@ -0,0 +1 @@ \ No newline at end of file Index: leahi_dialin/dd/constants.py =================================================================== diff -u --- leahi_dialin/dd/constants.py (revision 0) +++ leahi_dialin/dd/constants.py (revision 2138d06d100fdcf23f2e9069f35ee2fdee62008f) @@ -0,0 +1,18 @@ +########################################################################### +# +# 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 constants.py +# +# @author (last) Michael +# @date (last) 14-Nov-2024 +# @author (original) Michael +# @date (original) 14-Nov-2024 +# +############################################################################ + +RESET = 1 +NO_RESET = 0 Index: leahi_dialin/dd/dialysate_delivery.py =================================================================== diff -u --- leahi_dialin/dd/dialysate_delivery.py (revision 0) +++ leahi_dialin/dd/dialysate_delivery.py (revision 2138d06d100fdcf23f2e9069f35ee2fdee62008f) @@ -0,0 +1 @@ \ No newline at end of file Index: leahi_dialin/dd/pressure_sensors.py =================================================================== diff -u --- leahi_dialin/dd/pressure_sensors.py (revision 0) +++ leahi_dialin/dd/pressure_sensors.py (revision 2138d06d100fdcf23f2e9069f35ee2fdee62008f) @@ -0,0 +1 @@ \ No newline at end of file Index: leahi_dialin/dd/td_proxy.py =================================================================== diff -u --- leahi_dialin/dd/td_proxy.py (revision 0) +++ leahi_dialin/dd/td_proxy.py (revision 2138d06d100fdcf23f2e9069f35ee2fdee62008f) @@ -0,0 +1 @@ \ No newline at end of file Index: leahi_dialin/dd/valves.py =================================================================== diff -u --- leahi_dialin/dd/valves.py (revision 0) +++ leahi_dialin/dd/valves.py (revision 2138d06d100fdcf23f2e9069f35ee2fdee62008f) @@ -0,0 +1 @@ \ No newline at end of file Index: leahi_dialin/protocols/CAN.py =================================================================== diff -u --- leahi_dialin/protocols/CAN.py (revision 0) +++ leahi_dialin/protocols/CAN.py (revision 2138d06d100fdcf23f2e9069f35ee2fdee62008f) @@ -0,0 +1,909 @@ +########################################################################### +# +# 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 CAN.py +# +# @author (last) Micahel Garthwaite +# @date (last) 30-Jun-2023 +# @author (original) Peter Lucia +# @date (original) 02-Apr-2020 +# +############################################################################ + +import threading +import time +from collections import deque +import asyncio +from typing import Callable + +import can +from can.interfaces import socketcan +import math +from time import sleep +from datetime import datetime +import sys +from logging import Logger +import struct +from .. import common +from ..common import MsgIds +from ..utils import SingletonMeta, IntervalTimer +from concurrent.futures import ThreadPoolExecutor + +import os + +class DenaliMessage: + BYTE_ORDER = 'little' + START_BYTE = 0xA5 + START_INDEX = 0 + MSG_SEQ_INDEX = 1 + MSG_ID_INDEX = 3 + PAYLOAD_LENGTH_INDEX = 5 + PAYLOAD_START_INDEX = 6 + PAYLOAD_LENGTH_FIRST_PACKET = 1 + HEADER_LENGTH = 6 + PACKET_LENGTH = 8 + CRC_LENGTH = 1 + MAX_MSG_ID_NUMBER = 65535 + MAX_NUMBER_OF_PAYLOAD_BYTES = 254 + CRC_LIST = [ + 0, 49, 98, 83, 196, 245, 166, 151, 185, 136, 219, 234, 125, 76, 31, 46, + 67, 114, 33, 16, 135, 182, 229, 212, 250, 203, 152, 169, 62, 15, 92, 109, + 134, 183, 228, 213, 66, 115, 32, 17, 63, 14, 93, 108, 251, 202, 153, 168, + 197, 244, 167, 150, 1, 48, 99, 82, 124, 77, 30, 47, 184, 137, 218, 235, + 61, 12, 95, 110, 249, 200, 155, 170, 132, 181, 230, 215, 64, 113, 34, 19, + 126, 79, 28, 45, 186, 139, 216, 233, 199, 246, 165, 148, 3, 50, 97, 80, + 187, 138, 217, 232, 127, 78, 29, 44, 2, 51, 96, 81, 198, 247, 164, 149, + 248, 201, 154, 171, 60, 13, 94, 111, 65, 112, 35, 18, 133, 180, 231, 214, + 122, 75, 24, 41, 190, 143, 220, 237, 195, 242, 161, 144, 7, 54, 101, 84, + 57, 8, 91, 106, 253, 204, 159, 174, 128, 177, 226, 211, 68, 117, 38, 23, + 252, 205, 158, 175, 56, 9, 90, 107, 69, 116, 39, 22, 129, 176, 227, 210, + 191, 142, 221, 236, 123, 74, 25, 40, 6, 55, 100, 85, 194, 243, 160, 145, + 71, 118, 37, 20, 131, 178, 225, 208, 254, 207, 156, 173, 58, 11, 88, 105, + 4, 53, 102, 87, 192, 241, 162, 147, 189, 140, 223, 238, 121, 72, 27, 42, + 193, 240, 163, 146, 5, 52, 103, 86, 120, 73, 26, 43, 188, 141, 222, 239, + 130, 179, 224, 209, 70, 119, 36, 21, 59, 10, 89, 104, 255, 206, 157, 172 + ] + + _seq_num = 1 + + @staticmethod + def build_basic_message(channel_id=0, message=None): + """ + Builds a basic message dictionary containing the channel id and message + + @param channel_id: (int) indicates the channel + @param message: (list) integers forming the message + @return:: dictionary with channel_id and message keys + """ + if message is None: + message = [] + return {'channel_id': channel_id, 'message': message} + + @classmethod + def build_message(cls, channel_id=0, message_id=0, payload=None, seq=None): + """ + Builds a Denali message + + @param channel_id: (int) indicates the channel + @param message_id: (int) indicating the request type + @param payload: (list) contains the payload + @param seq: (int) Overrides current sequence number if set + @return:: dictionary with channel_id and 8-byte padded message + """ + + if payload is None: + payload = [] + + message_list = [DenaliCanMessenger.START_BYTE] + + if 0 <= message_id <= DenaliMessage.MAX_MSG_ID_NUMBER: + if seq is None: + # Wrap sequence number if it hits a max int16 + if cls._seq_num >= 32767: + cls._seq_num = 1 + + seq = cls._seq_num + + if message_id not in common.msg_defs.ACK_NOT_REQUIRED: + seq *= -1 + + message_seq_in_bytes = seq.to_bytes(2, byteorder=DenaliMessage.BYTE_ORDER, signed=True) + + message_list += [message_seq_in_bytes[0]] + message_list += [message_seq_in_bytes[1]] + + # Add message ID as unsigned 16-bit # + message_id_in_bytes = message_id.to_bytes(2, byteorder=DenaliMessage.BYTE_ORDER) + + message_list += [message_id_in_bytes[0]] + message_list += [message_id_in_bytes[1]] + + cls._seq_num += 1 + + else: + + return [] + + # Check payload length + payload_length = len(payload) + + # if payload is larger than 255 return nothing + if payload_length <= DenaliMessage.MAX_NUMBER_OF_PAYLOAD_BYTES: + # payload has to be a list + message_list += [payload_length] + else: + return [] + + message_list += payload + + # Because CRC does not include first byte, then we pass a list with out it + message_list += [DenaliMessage.crc8(message_list[1:])] + + message_list = DenaliMessage.pad_message_with_zeros(message_list) + + return DenaliMessage.build_basic_message(channel_id=channel_id, message=message_list) + + @staticmethod + def crc8(message_list): + """ + Returns the calculated crc from a message list + + @param message_list: is a list of integer numbers containing the message + @return:: integer containing a unsigned byte + """ + crc = 0 + for byte in message_list: + unsigned_byte = byte ^ crc + crc = DenaliMessage.CRC_LIST[unsigned_byte] + + return crc + + @staticmethod + def pad_message_with_zeros(message): + """ + Returns a packet padded with zeros that guarantees that the packet is a multiple of 8 bytes. + + @param message: packet that may or may not be multiple of 8 bytes + @return:: packet that is 8-byte multiple + + """ + message_length = len(message) + + # message must be multiple of 8 + if message_length % DenaliMessage.PACKET_LENGTH != 0: + # We need to pad the message with trailing zeros + add_these_many_zeros = math.ceil(message_length / DenaliMessage.PACKET_LENGTH) * \ + DenaliMessage.PACKET_LENGTH - message_length + + message += [0] * add_these_many_zeros + + return message + + @staticmethod + def get_crc(message): + """ + Gets the CRC in message + + @param message: Dialin complete message with CRC + + @return:: CRC in message + """ + + crc_index = DenaliMessage.PAYLOAD_START_INDEX + DenaliMessage.get_payload_length(message) + + return message['message'][crc_index] + + @staticmethod + def verify_crc(message): + """ + Verifies message CRC equals calculated message CRC + + @return:: TRUE if CRC matches, FALSE otherwise + """ + + if message is None: + return False + else: + message_list = message['message'] + + message_length = DenaliMessage.PAYLOAD_START_INDEX + DenaliMessage.get_payload_length(message) + calculated_crc = DenaliMessage.crc8(message_list[1:message_length]) + actual_crc = DenaliMessage.get_crc(message) + + return calculated_crc == actual_crc + + @staticmethod + def get_channel_id(message: dict) -> int: + """ + Returns request ID from message + + @param message: dictionary with channel_id and message keys + @return:: integer with channel id + + """ + + return message['channel_id'] + + @staticmethod + def get_sequence_number(message: dict) -> int: + """ + Returns sequence number from the message + + @param message: dictionary containing the message + @return:: (int) the sequence number + """ + seq = message['message'][DenaliMessage.MSG_SEQ_INDEX:DenaliMessage.MSG_ID_INDEX] + return int.from_bytes(seq, byteorder=DenaliMessage.BYTE_ORDER, signed=True) + + @staticmethod + def create_ack_message(message: dict, passive_mode: bool = True): + """ + Negates the sequence number and replaces the original message's sequence number with the + negated sequence number to create the ACK message. + @param message: (dict) a complete leahi_dialin message + @param passive_mode: (dict) true if in passive mode, false otherwise + @return: (dict) ACK message for the input message + """ + message = message.copy() + + seq = struct.unpack('h', bytearray( + message['message'][DenaliMessage.MSG_SEQ_INDEX:DenaliMessage.MSG_ID_INDEX]))[0] + + # send back empty payload since this is an ACK + payload = bytearray() + + channel_id_rx = DenaliMessage.get_channel_id(message) + + if passive_mode: + channel_rx_tx_pairs = { + DenaliChannels.hd_alarm_broadcast_ch_id: None, + DenaliChannels.dg_alarm_broadcast_ch_id: None, + DenaliChannels.ui_alarm_broadcast_ch_id: None, + DenaliChannels.hd_to_dg_ch_id: None, + DenaliChannels.dg_to_hd_ch_id: None, + DenaliChannels.hd_to_ui_ch_id: None, + DenaliChannels.hd_sync_broadcast_ch_id: None, + DenaliChannels.dg_to_ui_ch_id: None, + DenaliChannels.dg_sync_broadcast_ch_id: None, + DenaliChannels.ui_to_hd_ch_id: None, + DenaliChannels.ui_sync_broadcast_ch_id: None, + DenaliChannels.hd_to_dialin_ch_id: DenaliChannels.dialin_to_hd_ch_id, + DenaliChannels.dg_to_dialin_ch_id: DenaliChannels.dialin_to_dg_ch_id, + DenaliChannels.ui_to_dialin_ch_id: DenaliChannels.dialin_to_ui_ch_id + } + else: + channel_rx_tx_pairs = { + DenaliChannels.hd_alarm_broadcast_ch_id: None, + DenaliChannels.dg_alarm_broadcast_ch_id: None, + DenaliChannels.ui_alarm_broadcast_ch_id: None, + DenaliChannels.hd_to_dg_ch_id: DenaliChannels.dialin_to_hd_ch_id, + DenaliChannels.dg_to_hd_ch_id: DenaliChannels.dialin_to_dg_ch_id, + DenaliChannels.hd_to_ui_ch_id: DenaliChannels.dialin_to_hd_ch_id, + DenaliChannels.hd_sync_broadcast_ch_id: None, + DenaliChannels.dg_to_ui_ch_id: DenaliChannels.dialin_to_dg_ch_id, + DenaliChannels.dg_sync_broadcast_ch_id: None, + DenaliChannels.ui_to_hd_ch_id: DenaliChannels.dialin_to_ui_ch_id, + DenaliChannels.ui_sync_broadcast_ch_id: DenaliChannels.dialin_to_ui_ch_id, + DenaliChannels.hd_to_dialin_ch_id: DenaliChannels.dialin_to_hd_ch_id, + DenaliChannels.dg_to_dialin_ch_id: DenaliChannels.dialin_to_dg_ch_id, + DenaliChannels.ui_to_dialin_ch_id: DenaliChannels.dialin_to_ui_ch_id + } + + channel_id_tx = channel_rx_tx_pairs.get(channel_id_rx, None) + + if channel_id_tx is None: + return None + + message = DenaliMessage.build_message( + channel_id=channel_id_tx, + message_id=common.msg_defs.MsgIds.MSG_ID_ACK_MESSAGE_THAT_REQUIRES_ACK.value, + payload=payload, + seq=-seq) + + return message + + @staticmethod + def get_message_id(message): + """ + Returns request ID from packet + @param message: complete Diality Packet + @return:: integer with request ID + + """ + msg_id_array = message['message'][DenaliMessage.MSG_ID_INDEX: + DenaliMessage.PAYLOAD_LENGTH_INDEX] + return int.from_bytes(msg_id_array, DenaliMessage.BYTE_ORDER) + + @staticmethod + def get_message_id_xstr(message): + """ + Returns request ID from packet in hex string + @param message: complete Diality Packet + @return:: integer with request ID + """ + msg_id = "" + for index in range(DenaliMessage.MSG_ID_INDEX, DenaliMessage.PAYLOAD_LENGTH_INDEX): + msg_id += "{0:02X}" .format(message['message'][index]) + return msg_id + + @staticmethod + def get_payload_length(message): + """ + Returns payload length from message + + @param message: dictionary with channel_id and message keys + @return:: a unsigned payload length + """ + return message['message'][DenaliMessage.PAYLOAD_LENGTH_INDEX] + + @staticmethod + def get_payload(message): + """ + Returns payload array from message + + @param message: dictionary with channel_id and message keys + @return:: a payload array if exist + """ + + payload_length = DenaliMessage.get_payload_length(message) + + if payload_length == 0: + return None + else: + return message['message'][DenaliMessage.PAYLOAD_START_INDEX:] + + @staticmethod + def get_total_packets(message, is_array=True): + """ + Returns the number of packets needed to transmit Denali Message + + @param message: dictionary with channel_id and message keys or raw message + @param is_array: True if message is an array and not a dictionary + @return:: number of packets + """ + the_message = message if is_array else message['message'] + + return math.ceil((the_message[DenaliMessage.PAYLOAD_LENGTH_INDEX] + + DenaliMessage.HEADER_LENGTH + DenaliMessage.CRC_LENGTH) / + DenaliMessage.PACKET_LENGTH) + + +class DenaliChannels: + """ + Convenience class listing all the possible CAN channels used in the Denali system. + Updates made to the "Message List.xlsx" document found in the Diality Software Team SharePoint, + specifically in the CAN Channels sheet, should also be applied here. + """ + + td_alarm_broadcast_ch_id = 0x001 + dd_alarm_broadcast_ch_id = 0x002 + ro_alarm_broadcast_ch_id = 0x004 + ui_alarm_broadcast_ch_id = 0x008 + td_to_dd_ch_id = 0x010 + dd_to_td_ch_id = 0x011 + dd_to_ro_ch_id = 0x020 + ro_to_dd_ch_id = 0x021 + td_to_ui_ch_id = 0x040 + ui_to_td_ch_id = 0x041 + td_sync_broadcast_ch_id = 0x100 + dd_sync_broadcast_ch_id = 0x101 + ro_sync_broadcast_ch_id = 0x102 + ui_sync_broadcast_ch_id = 0x103 + dialin_to_td_ch_id = 0x400 + td_to_dialin_ch_id = 0x401 + dialin_to_dd_ch_id = 0x402 + dd_to_dialin_ch_id = 0x403 + dialin_to_ro_ch_id = 0x404 + ro_to_dialin_ch_id = 0x405 + dialin_to_ui_ch_id = 0x406 + ui_to_dialin_ch_id = 0x407 + + +class LongDenaliMessageBuilder: + + def __init__(self, message: can.Message): + """ + LongDialityMessageBuilder is a utility object that helps construct a Denali message + that is longer than 8 bytes. It is only called when we don't yet have a long message + builder for the current channel. Basic principle is to construct an object with the + first 8 byte message which contains the length of the message, and the later push the + remaining messages. e.g., let's imagine a 3 message packet. + + obj = LongDialityMessageBuilder(msg1) + + message = obj.push(msg2), returns None, message is not complete + + message = obj.push(msg3), return the packet which is the concatenation of msg1, msg2 and msg3 + + @param message: a CAN message + """ + self.message_data = [b for b in message.data] + self.number_of_can_packets_needed = DenaliMessage.get_total_packets(self.message_data) + self.number_of_can_packets_up_to_now = 1 + + def push(self, message: can.Message, first_packet=False): + """ + push appends the CAN message to the current list of messages + + @param message: 8-byte message + + @param first_packet: True if it is the first packet received + + @return:: None if the packet is not completed, otherwise returns the complete packet + """ + message_data = [b for b in message.data] + if first_packet: + self.message_data = message_data + self.number_of_can_packets_needed = DenaliMessage.get_total_packets(message_data) + self.number_of_can_packets_up_to_now = 1 + + else: + self.message_data += message_data + self.number_of_can_packets_up_to_now += 1 + + if self.number_of_can_packets_up_to_now == self.number_of_can_packets_needed: + return_message = self.message_data + self.message_data = None + return return_message + + else: + return None + + +class DenaliCanMessenger(metaclass=SingletonMeta): + START_BYTE = DenaliMessage.START_BYTE + DIALIN_MSG_RESP_TO = 0.5 # number of seconds to wait for a response to a send command + + def __init__(self, can_interface: str, + logger: Logger, + passive_mode=True, + console_out=False): + """ + DenaliCanMessenger constructor + + @param can_interface - string containing the can interface, e.g., 'can0" + @return: DialityCanMessenger object + + """ + self.message_queue_mutex = threading.Lock() + self.response_dictionary_mutex = threading.Lock() + self.transmitting_mutex = threading.Lock() + self.logger = logger + self.message_queue = deque() + self.callback_listener_complete_messages = None + self.callback_listener_invalid_messages = None + self.thread_pool_executor = ThreadPoolExecutor(max_workers=1) + # TODO for debugging purposes + if os.path.exists('Listener_can_dump.log'): os.remove('Listener_can_dump.log') + self.temp_logger = open('Listener_can_dump.log', 'w') + if os.path.exists('Send_can_dump.log'): os.remove('Send_can_dump.log') + self.temp_send_logger = open('Send_can_dump.log', 'w') + if os.path.exists('dialin_processed_msg.log'): os.remove('dialin_processed_msg.log') + self.temp_dialin_processed_logger = open('dialin_processed_msg.log', 'w') + # TODO for debugging purposes + # try to setup can bus and exit if the can bus has not ben setup to use. + try: + self.bus = socketcan.SocketcanBus(channel=can_interface) + self.loop = asyncio.get_event_loop() + if self.bus is not None: + self.thread_canbus = threading.Thread(target=self.listener, daemon=True) + self.thread_message_queue = threading.Thread(target=self.handle_messages, daemon=True) + else: + self.thread_canbus = None + s = "Can connection is not valid" + self.logger.debug(s) + sys.exit(s) + self.listener_buffer = can.AsyncBufferedReader() + self.notifier = can.Notifier(bus=self.bus, listeners=[self.listener_buffer], loop=self.loop) + except Exception as e: + s = str(e) + self.logger.error(s) + print(s) + sys.exit(19) + + self.passive_mode = passive_mode + self.console_out = console_out + self.send_event = threading.Event() + self.long_message_builders = {} + self.long_msg_channel_id_set = set() + self.messages = None + self.command_response_message = None + self.response_channel_id = -1 + self.run = False + self.sync_response_dictionary = {} + self.ui_received_function_ptr = None + self.pending_requests = {} + self.transmit_interval_dictionary = {} + + def start(self): + """ + starts listening to the can interface. + + """ + + if self.bus is None: + self.logger.error("Cannot start can listener.") + return + else: + self.run = True + if self.thread_message_queue is not None and self.thread_canbus is not None: + if not self.thread_canbus.is_alive(): + self.thread_canbus.start() + self.logger.info("Canbus thread has started.") + if not self.thread_message_queue.is_alive(): + self.thread_message_queue.start() + self.logger.info("Message queue thread has started.") + else: + self.logger.error("Cannot start listener...") + + def stop(self): + """ + Stop listening the can interface + + """ + self.run = False + self.logger.debug("\nCan listener has stopped.") + + def listener(self): + """ + Listens for diality message on the can interface passed during construction. + """ + async def _listener(): + while True: + message = await self.listener_buffer.get_message() + + if message is not None: + data = str(time.time()) + ',' + str(message) + '\r' + self.temp_logger.write(data) + self.message_queue.append(message) + + else: # no new packets in receive buffer + # Careful here, making this any shorter will start limiting CPU time for other threads + sleep(0.01) + + if not self.loop.is_running(): + self.loop.run_until_complete(_listener()) + else: + self.loop.create_task(_listener()) + + def handle_messages(self): + """ + Handles messages added to the leahi_dialin canbus message queue + @return: None + """ + + while True: + + if not self.message_queue: + # Careful here, making this any shorter will start limiting CPU time for other threads + sleep(0.01) + else: + self.message_queue_mutex.acquire() + message: can.Message = self.message_queue.popleft() + + if message.dlc == DenaliMessage.PACKET_LENGTH: + # We have received a legit CAN message of 8 bytes + can_data = [b for b in message.data] + channel_id = message.arbitration_id + if not DenaliMessage.PAYLOAD_LENGTH_INDEX < len(can_data): + self.logger.error("Invalid Denali message received: {0}".format(message)) + self.messages = None # Can't process this message, get the next one + self.message_queue_mutex.release() + continue + else: + message_length = can_data[DenaliMessage.PAYLOAD_LENGTH_INDEX] + + # if we are building a long message, then proceed to push it to the channel dictionary + if channel_id in self.long_msg_channel_id_set: + self.messages = self.long_message_builders[channel_id].push(message) + + elif can_data[0] == DenaliMessage.START_BYTE and \ + message_length <= DenaliMessage.PAYLOAD_LENGTH_FIRST_PACKET: # This is a short packet + # This is the first time that we are building a message + self.messages = can_data # deliver the packet + + elif can_data[0] == self.START_BYTE and \ + message_length > DenaliMessage.PAYLOAD_LENGTH_FIRST_PACKET: # Long packet start + # We are starting to build a long message, include it in the lonMsgChannelIDSet + self.long_msg_channel_id_set.add(channel_id) + + # if we don't have a long Denali message builder yet, create it + if channel_id not in self.long_message_builders.keys(): + self.long_message_builders[channel_id] = LongDenaliMessageBuilder(message) + self.messages = None + + else: # if we do have a builder. This is the first time + self.long_message_builders[channel_id].push(message, first_packet=True) + self.messages = None + + # Do we have a complete (long or short) Denali Message? + if self.messages is not None: + message_valid = True # assume true for now, set to false if CRC check fails below + complete_dialin_message = DenaliMessage.build_basic_message(channel_id=channel_id, + message=self.messages) + dialin_msg_id = DenaliMessage.get_message_id(complete_dialin_message) + dialin_ch_id = DenaliMessage.get_channel_id(complete_dialin_message) + self.temp_dialin_processed_logger.write(str(complete_dialin_message)) + if dialin_ch_id in self.long_msg_channel_id_set: + # We need to remove channel ID from the long message set + self.long_msg_channel_id_set.remove(dialin_ch_id) + + # Need to verify CRC at this point + if not DenaliMessage.verify_crc(complete_dialin_message): + # if verify is False, let's drop (ignore) this message + message_valid = False + dialin_ch_id = None + dialin_msg_id = None + self.logger.critical( + "Incorrect CRC, received message: {}, crc: {}, calculated crc: {}\n".format( + self.messages, DenaliMessage.get_crc(complete_dialin_message), + DenaliMessage.crc8(self.messages))) + + if message_valid: + if self.console_out: + self.do_console_out(complete_dialin_message) + # Send an ack if required + if DenaliMessage.get_sequence_number(complete_dialin_message) < 0: + # ACK required. Send back the received message with the sequence sign bit flipped + msg = DenaliMessage.create_ack_message(message=complete_dialin_message, + passive_mode=False) + if msg is not None: + self.send(msg, 0, is_ack=True) + + # We first check if this is a response to a send request that is pending + if self.pending_requests and dialin_msg_id in self.pending_requests: + + self.pending_requests[dialin_msg_id] = complete_dialin_message + self.send_event.set() + + # If it is not, this is a publication message and we need to call it's register function + else: + self.response_dictionary_mutex.acquire() + + if DenaliCanMessenger.is_ui_received_channel(dialin_ch_id): # check if the channel is in ui channels + if self.ui_received_function_ptr is not None: + self.thread_pool_executor.submit( + self.ui_received_function_ptr, + complete_dialin_message, message.timestamp + ) + + if dialin_ch_id in self.sync_response_dictionary.keys() and \ + dialin_msg_id in self.sync_response_dictionary[channel_id].keys(): + for function_id in self.sync_response_dictionary[dialin_ch_id][dialin_msg_id]: + self.thread_pool_executor.submit( + self.sync_response_dictionary[dialin_ch_id][dialin_msg_id][function_id], + complete_dialin_message, message.timestamp) + + self.response_dictionary_mutex.release() + else: + self.logger.critical("Invalid message: {}\n".format(self.messages)) + + # Done with this message, let's get the next one + self.messages = None + self.message_queue_mutex.release() + + @staticmethod + def is_ui_received_channel(channel_id: int) -> bool: + """ + checks if the channel id, channel_id is ui channel. + @param channel_id: the channel id to check + @return: true, if the channel is the ui channel. + """ + if channel_id in { + DenaliChannels.ui_to_hd_ch_id, + DenaliChannels.ui_to_dg_ch_id, + DenaliChannels.ui_to_dialin_ch_id, + DenaliChannels.ui_sync_broadcast_ch_id, + DenaliChannels.ui_alarm_broadcast_ch_id + }: # check if the channel is in ui channels + return True + else: + return False + + def register_receiving_publication_function(self, channel_id, message_id, function): + """ + Assign a function with packet parameter to an sync request id, e.g., + def function(packet). + + @param channel_id: can channel number where messages are received + @param message_id: Diality request ID in message + @param function: function reference + """ + # function_id is a UID for each callback per channel,msg pair. + function_id = id(function) + # if the channel_id exist, we update the dictionary for the channel_id + self.response_dictionary_mutex.acquire() + if channel_id in self.sync_response_dictionary.keys(): + if message_id in self.sync_response_dictionary[channel_id].keys(): + self.sync_response_dictionary[channel_id][message_id].update({function_id: function}) + else: + self.sync_response_dictionary[channel_id].update( {message_id: {function_id: function}}) + + # otherwise, we need to create the dictionary for the channel_id, msg_id pair + else: + self.sync_response_dictionary[channel_id] = {message_id: {function_id: function}} + self.response_dictionary_mutex.release() + + def register_received_all_ui_publication_function(self, function_ptr: Callable): + """ + Assign a function with packet parameter to an sync request id, e.g., + def function(packet). + + @param function_ptr: function reference + """ + + self.ui_received_function_ptr = function_ptr + + def send(self, + built_message: dict, + time_out: float = DIALIN_MSG_RESP_TO, + resend: bool = False, + is_ack: bool = False): + """ + Sends a Denali message + + @param built_message: (dict) message built using DialinMessage class + @param time_out: (float) time it will wait for a response in seconds + @param resend: (bool) Allow resending the message when no response is received. Disabled by default + @param is_ack: (bool) If we're sending an ACK, False by default + @return: (dict) The Denali packet. If a timeout occurs returns None + """ + data = str(time.time()) + ',' + str(built_message) + '\r' + self.temp_send_logger.write(data) + msg_sent = False + msg_id = -1 + + # keep trying to send message until we get a response + while not msg_sent: + + channel_id = DenaliMessage.get_channel_id(built_message) + + padded_can_message_array = built_message['message'] + + msg_id = DenaliMessage.get_message_id(built_message) + if not is_ack and msg_id not in self.pending_requests: + self.pending_requests[msg_id] = None + + # A message can be longer than 8 bytes, so we need to split it + # into 8 bytes packets. + + number_of_packets = DenaliMessage.get_total_packets(padded_can_message_array) + + # We are sending one message at a time on CAN + self.transmitting_mutex.acquire() + + for n in range(number_of_packets): + packet = padded_can_message_array[n * DenaliMessage.PACKET_LENGTH: + (n + 1) * DenaliMessage.PACKET_LENGTH] + + # Sending one packet at a time + packet = can.Message(arbitration_id=channel_id, + data=packet, + is_extended_id=False) + + self.bus.send(packet, 0) # 0.1) + + self.transmitting_mutex.release() + + # After all messages have been sent, we clear a flag + self.send_event.clear() + + # Block until the timeout completes or until the threading event's flag is set in self.listener, + # indicating a response has been received + self.send_event.wait(time_out) + + # if we're sending an ack, nothing left to do + if is_ack: + return None + + # only resend the message if resend is enabled and we haven't received a response yet + if resend and self.pending_requests.get(msg_id, None) is None: + msg_sent = False + self.logger.debug("No response. Re-sending message.") + else: + msg_sent = True + + response = self.pending_requests.get(msg_id, None) + if response is not None: + del self.pending_requests[msg_id] + return response + + @staticmethod + def _format_message_candump_style(message: can.Message, channel: str, send: bool = True) -> str: + """ + Formats a packet + @param message: (can.Message) The packet to log + @param channel: (str) The channel send or received on + @param send: (bool) Whether we're sending or receiving this packet + @return: The styled message + """ + + tmp = str(message) + + data = tmp[-41:-18].upper() + if send: + data = tmp[-23:].upper() + + return " {0} {1} [{2}] {3}\n".format(channel, str(hex(message.arbitration_id)[2:]).zfill(3), message.dlc, data) + + def do_log_can(self, packet: can.Message, style="candump", channel="can0", send=True): + """ + Logs all packets sent or received by leahi_dialin in candump, or non-candump style format + + @param packet: (can.Message) The packet to log + @param style: (str) The style to log in (candump, non-candump) + @param channel: (str) The channel send or received on + @param send: (bool) Whether we're sending or receiving this packet + @return: None + """ + filename = "Dialin_CAN_Send.log" + if not send: + filename = "Dialin_CAN_Receive.log" + if style == "candump": + with open(filename, 'a') as f: + styled_message = self._format_message_candump_style(message=packet, channel=channel, send=send) + f.write(styled_message) + else: + with open(filename, 'a') as f: + f.write("{0}\n".format(packet)) + + @staticmethod + def convert_message_to_string(complete_dialin_message: dict) -> str: + """ + Converts the DenaliMessage to hex string (data len is not hex) + @param complete_dialin_message: the complete can message in dictionary + @return: + """ + channel = "{0:03X}" .format(DenaliMessage.get_channel_id(complete_dialin_message)) + msg_id = DenaliMessage.get_message_id_xstr(complete_dialin_message) + data_len = DenaliMessage.get_payload_length(complete_dialin_message) + length = "{0:02X}" .format(data_len) + data = "" + pram_len = 0 + if data_len != 0: + pram_len = int(data_len / 4) + for i in range(pram_len): + data += "{0:02X}".format(complete_dialin_message['message'][DenaliMessage.PAYLOAD_START_INDEX + i ]) + data += "{0:02X}".format(complete_dialin_message['message'][DenaliMessage.PAYLOAD_START_INDEX + i + 1]) + data += "{0:02X}".format(complete_dialin_message['message'][DenaliMessage.PAYLOAD_START_INDEX + i + 2]) + data += "{0:02X}".format(complete_dialin_message['message'][DenaliMessage.PAYLOAD_START_INDEX + i + 3]) + data += " " + message = "{} {} {} {}".format(channel, msg_id, data_len, data) + return message + + @staticmethod + def do_console_out(complete_dialin_message: dict) -> None: + """ + prints out the message in hex format similar to the candump + @return: None + """ + exception_msg_id = { + MsgIds.MSG_ID_UI_CHECK_IN.value, + MsgIds.MSG_ID_ACK_MESSAGE_THAT_REQUIRES_ACK + } + msg_id = DenaliMessage.get_message_id(complete_dialin_message) + if msg_id in exception_msg_id: + return + + message = "# " + DenaliCanMessenger.convert_message_to_string(complete_dialin_message) + print(message) + + def register_transmitting_interval_message(self, interval: float, function) ->None: + """ + registers a callback function with a specified time interval to a dictionary + @return: None + """ + function_id = id(function) + + if function_id in self.transmit_interval_dictionary.keys(): + self.logger.error("ERROR: Attempting to assign more than one timed interval per given method.") + self.transmit_interval_dictionary[function_id].stop() + self.transmit_interval_dictionary[function_id].start() + else: + self.transmit_interval_dictionary[function_id] = IntervalTimer(interval, function) + + return function_id Index: leahi_dialin/protocols/__init__.py =================================================================== diff -u --- leahi_dialin/protocols/__init__.py (revision 0) +++ leahi_dialin/protocols/__init__.py (revision 2138d06d100fdcf23f2e9069f35ee2fdee62008f) @@ -0,0 +1,4 @@ +from .CAN import DenaliCanMessenger +from .CAN import DenaliChannels +from .CAN import LongDenaliMessageBuilder +from .CAN import DenaliMessage Index: leahi_dialin/ro/__init__.py =================================================================== diff -u --- leahi_dialin/ro/__init__.py (revision 0) +++ leahi_dialin/ro/__init__.py (revision 2138d06d100fdcf23f2e9069f35ee2fdee62008f) @@ -0,0 +1 @@ \ No newline at end of file Index: leahi_dialin/ro/constants.py =================================================================== diff -u --- leahi_dialin/ro/constants.py (revision 0) +++ leahi_dialin/ro/constants.py (revision 2138d06d100fdcf23f2e9069f35ee2fdee62008f) @@ -0,0 +1,18 @@ +########################################################################### +# +# 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 constants.py +# +# @author (last) Michael +# @date (last) 14-Nov-2024 +# @author (original) Michael +# @date (original) 14-Nov-2024 +# +############################################################################ + +RESET = 1 +NO_RESET = 0 Index: leahi_dialin/ro/reverse_osmosis.py =================================================================== diff -u --- leahi_dialin/ro/reverse_osmosis.py (revision 0) +++ leahi_dialin/ro/reverse_osmosis.py (revision 2138d06d100fdcf23f2e9069f35ee2fdee62008f) @@ -0,0 +1 @@ \ No newline at end of file Index: leahi_dialin/td/__init__.py =================================================================== diff -u --- leahi_dialin/td/__init__.py (revision 0) +++ leahi_dialin/td/__init__.py (revision 2138d06d100fdcf23f2e9069f35ee2fdee62008f) @@ -0,0 +1 @@ \ No newline at end of file Index: leahi_dialin/td/constants.py =================================================================== diff -u --- leahi_dialin/td/constants.py (revision 0) +++ leahi_dialin/td/constants.py (revision 2138d06d100fdcf23f2e9069f35ee2fdee62008f) @@ -0,0 +1,24 @@ +########################################################################### +# +# 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 constants.py +# +# @author (last) Michael +# @date (last) 14-Nov-2024 +# @author (original) Michael +# @date (original) 14-Nov-2024 +# +############################################################################ + +RESET = 1 +NO_RESET = 0 + +BUTTON_PRESSED = 1 +BUTTON_RELEASED = 0 + +PUMP_CONTROL_MODE_CLOSED_LOOP = 0 +PUMP_CONTROL_MODE_OPEN_LOOP = 1 Index: leahi_dialin/td/modules/__init__.py =================================================================== diff -u --- leahi_dialin/td/modules/__init__.py (revision 0) +++ leahi_dialin/td/modules/__init__.py (revision 2138d06d100fdcf23f2e9069f35ee2fdee62008f) @@ -0,0 +1 @@ \ No newline at end of file Index: leahi_dialin/td/modules/air_pump.py =================================================================== diff -u --- leahi_dialin/td/modules/air_pump.py (revision 0) +++ leahi_dialin/td/modules/air_pump.py (revision 2138d06d100fdcf23f2e9069f35ee2fdee62008f) @@ -0,0 +1,140 @@ +########################################################################### +# +# Copyright (c) 2022-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 air_pump.py +# +# @author (last) Micahel Garthwaite +# @date (last) 10-Mar-2023 +# @author (original) Micahel Garthwaite +# @date (original) 01-Nov-2024 +# +############################################################################ + +import struct +from logging import Logger + +from leahi_dialin.td.constants import RESET, NO_RESET +from leahi_dialin.common.msg_defs import MsgIds, MsgFieldPositions +from leahi_dialin.protocols.CAN import DenaliMessage, DenaliChannels +from leahi_dialin.utils.base import AbstractSubSystem, publish +from leahi_dialin.utils.checks import check_broadcast_interval_override_ms +from leahi_dialin.utils.conversions import integer_to_bytearray + + +class TDAirPump(AbstractSubSystem): + """ + TDAirPump + + Treatment Delivery (TD) Dialin API sub-class for air pump related commands. + """ + + def __init__(self, can_interface, logger: Logger): + """ + + @param can_interface: Denali Can Messenger object + """ + super().__init__() + self.can_interface = can_interface + self.logger = logger + + if self.can_interface is not None: + channel_id = DenaliChannels.td_sync_broadcast_ch_id + msg_id = MsgIds.MSG_ID_TD_AIR_PUMP_DATA.value + self.can_interface.register_receiving_publication_function(channel_id, msg_id, + self._handler_air_pump_sync) + + self.air_pump_state = 0 + self.td_air_pump_timestamp = 0.0 + + @publish(["td_air_pump_timestamp", "air_pump_state"]) + def _handler_air_pump_sync(self, message, timestamp=0.0): + """ + Handles published air pump data messages. + + @param message: published air pump data message as: air pump state + @return: None + """ + aps = struct.unpack('i', bytearray( + message['message'][MsgFieldPositions.START_POS_FIELD_1:MsgFieldPositions.END_POS_FIELD_1])) + + self.air_pump_state = aps[0] + self.td_air_pump_timestamp = timestamp + + def cmd_air_pump_set_state(self, state: int) -> int: + """ + Constructs and sends the air pump set state command. + AIR_PUMP_STATE_INIT = 0, ///< Air Pump Initialize state + AIR_PUMP_STATE_OFF, ///< Air Pump Off state + AIR_PUMP_STATE_ON, ///< Air Pump On state + NUM_OF_AIR_PUMP_STATES, ///< Number of air pump states + + Constraints: + Must be logged into TD. + + """ + + idx = integer_to_bytearray(state) + payload = idx + + message = DenaliMessage.build_message(channel_id=DenaliChannels.dialin_to_td_ch_id, + message_id=MsgIds.MSG_ID_TD_AIR_PUMP_SET_STATE_REQUEST.value, + payload=payload) + + self.logger.debug("setting air pump state to" + str(state)) + + # 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.debug("Timeout!!!!") + return False + + def cmd_air_pump_data_broadcast_interval_override(self, ms: int, reset: int = NO_RESET) -> int: + """ + Constructs and sends the air pump data broadcast interval override command + Constraints: + Must be logged into TD. + Given interval must be non-zero and a multiple of the TD general task interval (50 ms). + + @param ms: integer - interval (in ms) to override with + @param reset: integer - 1 to reset a previous override, 0 to override + @return: 1 if successful, zero otherwise + """ + + if not check_broadcast_interval_override_ms(ms): + return False + + rst = integer_to_bytearray(reset) + mis = integer_to_bytearray(ms) + payload = rst + mis + + message = DenaliMessage.build_message(channel_id=DenaliChannels.dialin_to_td_ch_id, + message_id=MsgIds.MSG_ID_TD_AIR_PUMP_PUBLISH_INTERVAL_OVERRIDE_REQUEST.value, + payload=payload) + + self.logger.debug("override TD air pump data broadcast interval") + + # Send message + received_message = self.can_interface.send(message) + + # If there is content... + if received_message is not None: + if reset == RESET: + str_res = "reset back to normal: " + else: + str_res = str(ms) + " ms: " + self.logger.debug("Air pump data broadcast interval overridden to " + str_res + + str(received_message['message'][DenaliMessage.PAYLOAD_START_INDEX])) + # response payload is OK or not OK + return received_message['message'][DenaliMessage.PAYLOAD_START_INDEX] + else: + self.logger.debug("Timeout!!!!") + return False Index: leahi_dialin/td/modules/air_trap.py =================================================================== diff -u --- leahi_dialin/td/modules/air_trap.py (revision 0) +++ leahi_dialin/td/modules/air_trap.py (revision 2138d06d100fdcf23f2e9069f35ee2fdee62008f) @@ -0,0 +1,244 @@ +########################################################################### +# +# 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 air_trap.py +# +# @author (last) Michael Garthwaite +# @date (last) 26-Jun-2024 +# @author (original) Sean Nash +# @date (original) 21-Sep-2020 +# +############################################################################ +import struct +from logging import Logger +from enum import unique + +from leahi_dialin.td.constants import RESET, NO_RESET +from leahi_dialin.common.msg_defs import MsgIds, MsgFieldPositions +from leahi_dialin.protocols.CAN import DenaliMessage, DenaliChannels +from leahi_dialin.utils.base import AbstractSubSystem, publish, DialinEnum +from leahi_dialin.utils.checks import check_broadcast_interval_override_ms +from leahi_dialin.utils.conversions import integer_to_bytearray + + +class AirTrapValves(DialinEnum): + VBT = 0 + +@unique +class AirTrapState(DialinEnum): + STATE_CLOSED = 0 + STATE_OPEN = 1 + +class TDAirTrap(AbstractSubSystem): + """ + TDAirTrap + + Treatment Delivery (TD) Dialin API sub-class for air trap related commands. + """ + + # Air trap level sensor IDs + LOWER_LEVEL_SENSOR = 0 + UPPER_LEVEL_SENSOR = 1 + + # Air trap level sensor levels + AIR_DETECTED_AT_LEVEL = 0 + FLUID_DETECTED_AT_LEVEL = 1 + + def __init__(self, can_interface, logger: Logger): + """ + + @param can_interface: Denali Can Messenger object + """ + super().__init__() + self.can_interface = can_interface + self.logger = logger + + if self.can_interface is not None: + channel_id = DenaliChannels.td_sync_broadcast_ch_id + msg_id = MsgIds.MSG_ID_TD_AIR_TRAP_DATA.value + self.can_interface.register_receiving_publication_function(channel_id, msg_id, + self._handler_air_trap_sync) + + self.lower_level = self.AIR_DETECTED_AT_LEVEL + self.upper_level = self.AIR_DETECTED_AT_LEVEL + self.lower_level_raw = self.AIR_DETECTED_AT_LEVEL + self.upper_level_raw = self.AIR_DETECTED_AT_LEVEL + self.td_air_trap_timestamp = 0.0 + + @publish(["td_air_trap_timestamp", "lower_level", "upper_level", "lower_level_raw", "upper_level_raw"]) + def _handler_air_trap_sync(self, message, timestamp=0.0): + """ + Handles published air trap data messages. Air trap data are captured + for reference. + + @param message: published air trap data message + @return: None + """ + + lower = struct.unpack('i', bytearray( + message['message'][MsgFieldPositions.START_POS_FIELD_1:MsgFieldPositions.END_POS_FIELD_1])) + upper = struct.unpack('i', bytearray( + message['message'][MsgFieldPositions.START_POS_FIELD_2:MsgFieldPositions.END_POS_FIELD_2])) + raw_lower = struct.unpack('i', bytearray( + message['message'][MsgFieldPositions.START_POS_FIELD_3:MsgFieldPositions.END_POS_FIELD_3])) + raw_upper = struct.unpack('i', bytearray( + message['message'][MsgFieldPositions.START_POS_FIELD_4:MsgFieldPositions.END_POS_FIELD_4])) + + self.lower_level = lower[0] + self.upper_level = upper[0] + self.lower_level_raw = raw_lower[0] + self.upper_level_raw = raw_upper[0] + self.td_air_trap_timestamp = timestamp + + def cmd_air_trap_level_sensor_override(self, sensor: int, detected: int, reset: int = NO_RESET) -> int: + """ + Constructs and sends the air trap level sensor override command + Constraints: + Must be logged into TD. + Given sensor must be one of the sensors listed below. + + @param sensor: unsigned int - sensor ID + @param detected: unsigned int - detected (0=air, 1=fluid) to override sensor with + @param reset: integer - 1 to reset a previous override, 0 to override + @return: 1 if successful, zero otherwise + + Air trap sensor IDs: \n + 0 = Lower level \n + 1 = Upper level \n + """ + + rst = integer_to_bytearray(reset) + det = integer_to_bytearray(detected) + idx = integer_to_bytearray(sensor) + payload = rst + det + idx + + message = DenaliMessage.build_message(channel_id=DenaliChannels.dialin_to_td_ch_id, + message_id=MsgIds.MSG_ID_TD_AIR_TRAP_LEVEL_OVERRIDE_REQUEST.value, + payload=payload) + + self.logger.debug("override air trap level sensor detection value for sensor " + str(sensor) + " ,Level: " + str(detected)) + + # 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.debug("Timeout!!!!") + return False + + def cmd_raw_air_trap_level_sensor_override(self, sensor: int, detected: int, reset: int = NO_RESET) -> int: + """ + Constructs and sends the raw air trap level sensor override command + Constraints: + Must be logged into TD. + Given sensor must be one of the sensors listed below. + + @param sensor: unsigned int - sensor ID + @param detected: unsigned int - detected (0=air, 1=fluid) to override sensor with + @param reset: integer - 1 to reset a previous override, 0 to override + @return: 1 if successful, zero otherwise + + Air trap sensor IDs: \n + 0 = Lower level \n + 1 = Upper level \n + """ + + rst = integer_to_bytearray(reset) + det = integer_to_bytearray(detected) + idx = integer_to_bytearray(sensor) + payload = rst + det + idx + + message = DenaliMessage.build_message(channel_id=DenaliChannels.dialin_to_td_ch_id, + message_id=MsgIds.MSG_ID_TD_AIR_TRAP_LEVEL_RAW_OVERRIDE_REQUEST.value, + payload=payload) + + self.logger.debug("override raw air trap level sensor detection value for sensor " + str(sensor) + " ,Level: " + str(detected)) + + # 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.debug("Timeout!!!!") + return False + + def cmd_air_trap_data_broadcast_interval_override(self, ms: int, reset: int = NO_RESET) -> int: + """ + Constructs and sends the air trap data broadcast interval override command + Constraints: + Must be logged into TD. + Given interval must be non-zero and a multiple of the TD general task interval (50 ms). + + @param ms: integer - interval (in ms) to override with + @param reset: integer - 1 to reset a previous override, 0 to override + @return: 1 if successful, zero otherwise + """ + + if not check_broadcast_interval_override_ms(ms): + return False + + rst = integer_to_bytearray(reset) + mis = integer_to_bytearray(ms) + payload = rst + mis + + message = DenaliMessage.build_message(channel_id=DenaliChannels.dialin_to_td_ch_id, + message_id=MsgIds.MSG_ID_TD_AIR_TRAP_PUBLISH_INTERVAL_OVERRIDE_REQUEST.value, + payload=payload) + + self.logger.debug("override TD air trap data broadcast interval") + + # Send message + received_message = self.can_interface.send(message) + + # If there is content... + if received_message is not None: + if reset == RESET: + str_res = "reset back to normal: " + else: + str_res = str(ms) + " ms: " + self.logger.debug("Air trap data broadcast interval overridden to " + str_res + + str(received_message['message'][DenaliMessage.PAYLOAD_START_INDEX])) + # response payload is OK or not OK + return received_message['message'][DenaliMessage.PAYLOAD_START_INDEX] + else: + self.logger.debug("Timeout!!!!") + return False + + def cmd_set_hd_air_trap_valve(self, valve: int = AirTrapValves.VBT.value, + valve_state: int = AirTrapState.STATE_CLOSED.value) -> int: + """ + Constructs and sends an open/close command to the TD air trap valve + + @param valve: air trap valve ID ( VBT = 0 ) + @param valve_state: air trap valve state (open or close) + @returns 1 if successful, zero otherwise + """ + + vlv = integer_to_bytearray(valve) + sts = integer_to_bytearray(valve_state) + payload = sts + vlv + + message = DenaliMessage.build_message(channel_id=DenaliChannels.dialin_to_td_ch_id, + message_id=MsgIds.MSG_ID_TD_2_WAY_VALVE_SET_STATE_REQUEST.value, + payload=payload) + # Send message + received_message = self.can_interface.send(message) + + # If there is content... + if received_message is not None: + self.logger.debug("Opening air trap valve") + # response payload is OK or not OK + return received_message['message'][DenaliMessage.PAYLOAD_START_INDEX] + else: + self.logger.debug("Opening air trap valve timeout!!") + return False Index: leahi_dialin/td/modules/alarms.py =================================================================== diff -u --- leahi_dialin/td/modules/alarms.py (revision 0) +++ leahi_dialin/td/modules/alarms.py (revision 2138d06d100fdcf23f2e9069f35ee2fdee62008f) @@ -0,0 +1,693 @@ +########################################################################### +# +# 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 alarms.py +# +# @author (last) Michael Garthwaite +# @date (last) 04-Oct-2023 +# @author (original) Peter Lucia +# @date (original) 02-Apr-2020 +# +############################################################################ +import struct +from logging import Logger +from enum import unique +from leahi_dialin.utils.base import DialinEnum + +from leahi_dialin.td.constants import RESET, NO_RESET +from leahi_dialin.common.msg_defs import MsgIds, MsgFieldPositions +from leahi_dialin.common.td_defs import TDEventDataType +from leahi_dialin.protocols.CAN import DenaliMessage, DenaliChannels +from leahi_dialin.utils.base import AbstractSubSystem, publish +from leahi_dialin.utils.checks import check_broadcast_interval_override_ms +from leahi_dialin.utils.conversions import integer_to_bytearray, float_to_bytearray + + +class TDAlarms(AbstractSubSystem): + """ + TD interface containing alarm related commands. + """ + + # Alarm lamp patterns + TD_ALARM_LAMP_PATTERN_OFF = 0 + TD_ALARM_LAMP_PATTERN_OK = 1 + TD_ALARM_LAMP_PATTERN_FAULT = 2 + TD_ALARM_LAMP_PATTERN_HIGH = 3 + TD_ALARM_LAMP_PATTERN_MEDIUM = 4 + TD_ALARM_LAMP_PATTERN_LOW = 5 + TD_ALARM_LAMP_PATTERN_MANUAL = 6 + + # Alarm priority states + TD_ALARM_STATE_NONE = 0 + TD_ALARM_STATE_LOW = 1 + TD_ALARM_STATE_MEDIUM = 2 + TD_ALARM_STATE_HIGH = 3 + + # Alarm response buttons + @unique + class AlarmResponseButtons(DialinEnum): + TD_ALARM_RESPONSE_BUTTON_RESUME = 0 + TD_ALARM_RESPONSE_BUTTON_RINSEBACK = 1 + TD_ALARM_RESPONSE_BUTTON_END_TREATMENT = 2 + NUM_OF_TD_ALARM_RESPONSE_BUTTONS = 3 + + # Alarm status message field positions + START_POS_ALARM_STATE = DenaliMessage.PAYLOAD_START_INDEX + END_POS_ALARM_STATE = START_POS_ALARM_STATE + 4 + START_POS_ALARM_TOP = END_POS_ALARM_STATE + END_POS_ALARM_TOP = START_POS_ALARM_TOP + 4 + START_POS_ALARM_ESCALATES_IN = END_POS_ALARM_TOP + END_POS_ALARM_ESCALATES_IN = START_POS_ALARM_ESCALATES_IN + 4 + START_POS_ALARM_SILENCE_EXPIRES_IN = END_POS_ALARM_ESCALATES_IN + END_POS_ALARM_SILENCE_EXPIRES_IN = START_POS_ALARM_SILENCE_EXPIRES_IN + 4 + START_POS_ALARMS_FLAGS = END_POS_ALARM_SILENCE_EXPIRES_IN + END_POS_ALARMS_FLAGS = START_POS_ALARMS_FLAGS + 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 + + if self.can_interface is not None: + channel_id = DenaliChannels.td_alarm_broadcast_ch_id + msg_id = MsgIds.MSG_ID_ALARM_STATUS_DATA.value + self.can_interface.register_receiving_publication_function(channel_id, msg_id, + self._handler_alarms_status_sync) + + channel_id = DenaliChannels.td_alarm_broadcast_ch_id + msg_id = MsgIds.MSG_ID_ALARM_TRIGGERED.value + self.can_interface.register_receiving_publication_function(channel_id, msg_id, + self._handler_alarm_trigger) + msg_id = MsgIds.MSG_ID_ALARM_CLEARED.value + self.can_interface.register_receiving_publication_function(channel_id, msg_id, + self._handler_alarm_clear) + msg_id = MsgIds.MSG_ID_ALARM_CONDITION_CLEARED.value + self.can_interface.register_receiving_publication_function(channel_id, msg_id, + self._handler_alarm_condition_clear) + channel_id = DenaliChannels.td_sync_broadcast_ch_id + msg_id = MsgIds.MSG_ID_TD_ALARM_INFORMATION_DATA.value + self.can_interface.register_receiving_publication_function(channel_id, msg_id, + self._handler_alarm_information_sync) + + self.td_alarm_status_timestamp = 0.0 + self.td_alarm_triggered_timestamp = 0.0 + self.td_alarm_cleared_timestamp = 0.0 + self.td_alarm_clr_condition_timestamp = 0.0 + self.td_alarm_information_timestamp = 0.0 + # composite alarm status based on latest TD alarm status broadcast message + self.alarms_priority_state = 0 + self.alarm_top = 0 + self.alarms_silence_expires_in = 0 + self.alarms_escalates_in = 0 + self.alarms_flags = 0 + + # alarm states based on received TD alarm activation and alarm clear messages + self.alarm_states = [False] * 500 + # alarm condition states based on received TD alarm activation and clear condition messages + self.alarm_conditions = [False] * 500 + # alarm priorities based on received TD alarm activation messages + self.alarm_priorities = [0] * 500 + # alarm ranks based on received TD alarm activation messages + self.alarm_ranks = [0] * 500 + # alarm clear top only flags based on received TD alarm activation messages + self.alarm_clear_top_only_flags = [False] * 500 + # alarm debug data on alarm triggered message + self.alarm_data = [0, 0] * 500 + + # alarm information + self.alarm_data_type = dict() + self.alarm_volume = 0 + self.alarm_audio_curr_hg = 0.0 + self.alarm_audio_curr_lg = 0.0 + self.alarm_backup_audio_curr = 0.0 + self.safety_shutdown_active = False + self.ac_power_lost = False + self.alarm_table_button_blockers = [False] * self.AlarmResponseButtons.NUM_OF_TD_ALARM_RESPONSE_BUTTONS.value + self.alarm_state_button_blockers = [False] * self.AlarmResponseButtons.NUM_OF_TD_ALARM_RESPONSE_BUTTONS.value + + # Loop through the list of the event data type enum and update the dictionary + for data_type in TDEventDataType: + event_data_type = TDEventDataType(data_type).name + struct_unpack_type = None + + # If U32 is in the data type enum (i.e. EVENT_DATA_TYPE_U32), then the key is the enum and the value is + # the corresponding format in the python struct + if 'U32' in event_data_type or 'BOOL' in event_data_type or 'NONE' in event_data_type: + struct_unpack_type = 'I' + elif 'S32' in event_data_type: + struct_unpack_type = 'i' + elif 'F32' in event_data_type: + struct_unpack_type = 'f' + + self.alarm_data_type[event_data_type] = struct_unpack_type + + def clear_dialin_alarms(self): + """ + Clears the alarms states in Dialin. + + @return: none + """ + for x in range(500): + self.alarm_states[x] = False + + @publish(["td_alarm_status_timestamp", "alarms_priority_state", "alarm_top", "alarms_silence_expires_in", "alarms_escalates_in", "alarms_flags"]) + def _handler_alarms_status_sync(self, message, timestamp=0.0): + """ + Handles published alarms status messages. alarms status data are captured + for reference. + + @param message: published alarm status data message + @return: none + """ + self.alarms_priority_state = struct.unpack('i', bytearray(message['message'][MsgFieldPositions.START_POS_FIELD_1:MsgFieldPositions.END_POS_FIELD_1]))[0] + self.alarm_top = struct.unpack('i', bytearray(message['message'][MsgFieldPositions.START_POS_FIELD_2:MsgFieldPositions.END_POS_FIELD_2]))[0] + self.alarms_escalates_in = struct.unpack('i', bytearray(message['message'][MsgFieldPositions.START_POS_FIELD_3:MsgFieldPositions.END_POS_FIELD_3]))[0] + self.alarms_silence_expires_in = struct.unpack('i', bytearray(message['message'][MsgFieldPositions.START_POS_FIELD_4:MsgFieldPositions.END_POS_FIELD_4]))[0] + self.alarms_flags = struct.unpack('H', bytearray(message['message'][MsgFieldPositions.START_POS_FIELD_5:MsgFieldPositions.START_POS_FIELD_5+2]))[0] + + self.td_alarm_status_timestamp = timestamp + + @publish(["td_alarm_triggered_timestamp", "alarm_states", "alarm_conditions", "alarm_data", + "alarm_priorities", "alarm_ranks", "alarm_clear_top_only_flags"]) + def _handler_alarm_trigger(self, message, timestamp=0.0): + """ + Handles published TD alarm activation messages. + + @param message: published TD alarm activation message + @return: none + """ + self.logger.debug("Alarm activated!") + alarm_id = struct.unpack('i', bytearray(message['message'][MsgFieldPositions.START_POS_FIELD_1:MsgFieldPositions.END_POS_FIELD_1])) + + data_typ_1 = struct.unpack('i', bytearray(message['message'][MsgFieldPositions.START_POS_FIELD_2:MsgFieldPositions.END_POS_FIELD_2])) + # Get the corresponding structure format + struct_data_type_1 = self.alarm_data_type[TDEventDataType(data_typ_1[0]).name] + # Get the data value by unpacking the data type + data_1 = struct.unpack(struct_data_type_1, bytearray(message['message'][MsgFieldPositions.START_POS_FIELD_3:MsgFieldPositions.END_POS_FIELD_3])) + + data_typ_2 = struct.unpack('i', bytearray(message['message'][MsgFieldPositions.START_POS_FIELD_4:MsgFieldPositions.END_POS_FIELD_4])) + # Get the corresponding structure format + struct_data_type_2 = self.alarm_data_type[TDEventDataType(data_typ_2[0]).name] + # Get the data value by unpacking the data type + data_2 = struct.unpack(struct_data_type_2, bytearray(message['message'][MsgFieldPositions.START_POS_FIELD_5:MsgFieldPositions.END_POS_FIELD_5])) + + priority = struct.unpack('i', bytearray(message['message'][MsgFieldPositions.START_POS_FIELD_6:MsgFieldPositions.END_POS_FIELD_6])) + rank = struct.unpack('i', bytearray(message['message'][MsgFieldPositions.START_POS_FIELD_7:MsgFieldPositions.END_POS_FIELD_7])) + clr_top_only = struct.unpack('i', bytearray(message['message'][MsgFieldPositions.START_POS_FIELD_8:MsgFieldPositions.END_POS_FIELD_8])) + + self.logger.debug("Alarm ID: %d %d %d" % (alarm_id[0], data_1[0], data_2[0])) + self.alarm_states[alarm_id[0]] = True + self.alarm_conditions[alarm_id[0]] = True + self.alarm_priorities[alarm_id[0]] = priority[0] + self.alarm_ranks[alarm_id[0]] = rank[0] + self.alarm_clear_top_only_flags[alarm_id[0]] = clr_top_only[0] + self.alarm_data[alarm_id[0]] = [data_1[0], data_2[0]] + self.td_alarm_triggered_timestamp = timestamp + + @publish(["TD_alarm_cleared_timestamp", "alarm_states", "alarm_conditions"]) + def _handler_alarm_clear(self, message, timestamp=0.0): + """ + Handles published TD alarm clear messages. + + @param message: published TD alarm clear message + @return: none + """ + alarm_id = struct.unpack('i', bytearray(message['message'][MsgFieldPositions.START_POS_FIELD_1:MsgFieldPositions.END_POS_FIELD_1])) + self.alarm_states[alarm_id[0]] = False + self.alarm_conditions[alarm_id[0]] = False + self.td_alarm_cleared_timestamp = timestamp + + @publish(["td_alarm_clr_condition_timestamp", "alarm_conditions", "alarm_conditions"]) + def _handler_alarm_condition_clear(self, message, timestamp=0.0): + """ + Handles published TD alarm clear alarm condition messages. + + @param message: published TD alarm clear alarm condition message + @return: none + """ + alarm_id = struct.unpack('i', bytearray(message['message'][MsgFieldPositions.START_POS_FIELD_1:MsgFieldPositions.END_POS_FIELD_1])) + self.alarm_conditions[alarm_id[0]] = False + self.td_alarm_clr_condition_timestamp = timestamp + + @publish(["td_alarm_information_timestamp", "alarm_volume", "alarm_audio_curr_hg", "alarm_audio_curr_lg", "alarm_backup_audio_curr", + "safety_shutdown_active", "ac_power_lost", "alarm_table_button_blockers", "alarm_state_button_blockers"]) + def _handler_alarm_information_sync(self, message, timestamp=0.0): + """ + Handles published TD alarm information broadcast messages. + + @param message: published TD alarm information message + @return: none + """ + + vol = struct.unpack('i', bytearray( + message['message'][MsgFieldPositions.START_POS_FIELD_1:MsgFieldPositions.END_POS_FIELD_1])) + ach = struct.unpack('f', bytearray( + message['message'][MsgFieldPositions.START_POS_FIELD_2:MsgFieldPositions.END_POS_FIELD_2])) + acl = struct.unpack('f', bytearray( + message['message'][MsgFieldPositions.START_POS_FIELD_3:MsgFieldPositions.END_POS_FIELD_3])) + bac = struct.unpack('f', bytearray( + message['message'][MsgFieldPositions.START_POS_FIELD_4:MsgFieldPositions.END_POS_FIELD_4])) + saf = struct.unpack('i', bytearray( + message['message'][MsgFieldPositions.START_POS_FIELD_5:MsgFieldPositions.END_POS_FIELD_5])) + acp = struct.unpack('i', bytearray( + message['message'][MsgFieldPositions.START_POS_FIELD_6:MsgFieldPositions.END_POS_FIELD_6])) + trs = struct.unpack('B', bytearray( + message['message'][MsgFieldPositions.START_POS_FIELD_7:MsgFieldPositions.START_POS_FIELD_7+1])) + trb = struct.unpack('B', bytearray( + message['message'][MsgFieldPositions.START_POS_FIELD_7+1:MsgFieldPositions.START_POS_FIELD_7+2])) + tet = struct.unpack('B', bytearray( + message['message'][MsgFieldPositions.START_POS_FIELD_7+2:MsgFieldPositions.START_POS_FIELD_7+3])) + srs = struct.unpack('B', bytearray( + message['message'][MsgFieldPositions.START_POS_FIELD_7+3:MsgFieldPositions.START_POS_FIELD_7+4])) + srb = struct.unpack('B', bytearray( + message['message'][MsgFieldPositions.START_POS_FIELD_7+4:MsgFieldPositions.START_POS_FIELD_7+5])) + set = struct.unpack('B', bytearray( + message['message'][MsgFieldPositions.START_POS_FIELD_7+5:MsgFieldPositions.START_POS_FIELD_7+6])) + + self.alarm_volume = vol[0] + self.alarm_audio_curr_hg = ach[0] + self.alarm_audio_curr_lg = acl[0] + self.alarm_backup_audio_curr = bac[0] + self.safety_shutdown_active = True if saf[0] == 1 else False + self.ac_power_lost = True if acp[0] == 1 else False + self.alarm_table_button_blockers[self.AlarmResponseButtons.TD_ALARM_RESPONSE_BUTTON_RESUME.value] = True if trs[0] == 1 else False + self.alarm_table_button_blockers[self.AlarmResponseButtons.TD_ALARM_RESPONSE_BUTTON_RINSEBACK.value] = True if trb[0] else False + self.alarm_table_button_blockers[self.AlarmResponseButtons.TD_ALARM_RESPONSE_BUTTON_END_TREATMENT.value] = True if tet[0] else False + self.alarm_state_button_blockers[self.AlarmResponseButtons.TD_ALARM_RESPONSE_BUTTON_RESUME.value] = True if srs[0] else False + self.alarm_state_button_blockers[self.AlarmResponseButtons.TD_ALARM_RESPONSE_BUTTON_RINSEBACK.value] = True if srb[0] else False + self.alarm_state_button_blockers[self.AlarmResponseButtons.TD_ALARM_RESPONSE_BUTTON_END_TREATMENT.value] = True if set[0] else False + self.td_alarm_information_timestamp = timestamp + + def cmd_alarm_state_override(self, alarm: int, state: int, reset: int = NO_RESET) -> int: + """ + Constructs and sends the alarm state override command + Constraints: + Must be logged into TD. + Given alarm must be valid. + If inactivating alarm, given alarm must be recoverable (clearable). + + @param alarm: integer - ID of alarm to override + @param state: integer - 1 for alarm active, 0 for alarm inactive + @param reset: integer - 1 to reset a previous override, 0 to override + @return: 1 if successful, zero otherwise + """ + + rst = integer_to_bytearray(reset) + sta = integer_to_bytearray(state) + alm = integer_to_bytearray(alarm) + payload = rst + sta + alm + + message = DenaliMessage.build_message(channel_id=DenaliChannels.dialin_to_td_ch_id, + message_id=MsgIds.MSG_ID_TD_ALARM_STATE_OVERRIDE_REQUEST.value, + payload=payload) + + # Send message + received_message = self.can_interface.send(message) + + # If there is content... + if received_message is not None: + # self.logger.debug(received_message) + if reset == RESET: + str_res = "reset back to normal" + else: + str_res = ("active" if state != 0 else "inactive") + self.logger.debug("Alarm " + str(alarm) + " " + str_res + ": " + + str(received_message['message'][DenaliMessage.PAYLOAD_START_INDEX])) + # response payload is OK or not OK + return received_message['message'][DenaliMessage.PAYLOAD_START_INDEX] + else: + self.logger.debug("Timeout!!!!") + return False + + def cmd_clear_all_alarms(self) -> int: + """ + Constructs and sends the clear all active alarms command. + This will clear even non-recoverable alarms. + Constraints: + Must be logged into TD. + + @return: 1 if successful, zero otherwise + """ + + key = integer_to_bytearray(-758926171) # 0xD2C3B4A5 + payload = key + + message = DenaliMessage.build_message(channel_id=DenaliChannels.dialin_to_td_ch_id, + message_id=MsgIds.MSG_ID_TD_ALARM_CLEAR_ALL_ALARMS_REQUEST.value, + payload=payload) + + # Send message + received_message = self.can_interface.send(message) + + # If there is content... + if received_message is not None: + self.logger.debug("All alarms cleared.") + # response payload is OK or not OK + return 1 == received_message['message'][DenaliMessage.PAYLOAD_START_INDEX] + return False + + def cmd_alarm_time_override(self, alarm: int, time_ms: int, reset: int = NO_RESET) -> int: + """ + Constructs and sends the alarm time override command + Constraints: + Must be logged into TD. + Given alarm must be valid. + + @param alarm: integer - ID of alarm to override + @param time_ms: integer - time (in ms) since alarm was activated + @param reset: integer - 1 to reset a previous override, 0 to override + @return: 1 if successful, zero otherwise + """ + + rst = integer_to_bytearray(reset) + ms = integer_to_bytearray(time_ms) + alm = integer_to_bytearray(alarm) + payload = rst + ms + alm + + message = DenaliMessage.build_message(channel_id=DenaliChannels.dialin_to_td_ch_id, + message_id=MsgIds.MSG_ID_TD_ALARM_START_TIME_OVERRIDE_REQUEST.value, + payload=payload) + + self.logger.debug("override alarm time since activated") + + # Send message + received_message = self.can_interface.send(message) + + # If there is content... + if received_message is not None: + # self.logger.debug(received_message) + if reset == RESET: + str_res = "reset back to normal" + else: + str_res = str(time_ms) + self.logger.debug("Alarm time since activated overridden to " + str_res + " ms: " + + str(received_message['message'][DenaliMessage.PAYLOAD_START_INDEX])) + # response payload is OK or not OK + return received_message['message'][DenaliMessage.PAYLOAD_START_INDEX] + else: + self.logger.debug("Timeout!!!!") + return False + + def cmd_alarm_lamp_pattern_override(self, pattern: int, reset: int = NO_RESET) -> int: + """ + Constructs and sends the alarm lamp pattern override command. + Constraints: + Must be logged into TD. + Given pattern must be one of the patterns listed below. + + @param pattern: integer - ID of alarm lamp pattern to override with + @param reset: integer - 1 to reset a previous override, 0 to override + @return: 1 if successful, zero otherwise + + Patterns: \n + 0 = off \n + 1 = ok \n + 2 = fault \n + 3 = high \n + 4 = medium \n + 5 = low \n + 6 = manual + """ + rst = integer_to_bytearray(reset) + pat = integer_to_bytearray(pattern) + payload = rst + pat + + message = DenaliMessage.build_message(channel_id=DenaliChannels.dialin_to_td_ch_id, + message_id=MsgIds.MSG_ID_TD_ALARM_LAMP_PATTERN_OVERRIDE_REQUEST.value, + payload=payload) + + self.logger.debug("Override Alarm Lamp Pattern") + + # Send message + received_message = self.can_interface.send(message) + + # If there is content... + if received_message is not None: + self.logger.debug(received_message) + if reset == RESET: + str_pat = "reset back to normal" + elif pattern == self.TD_ALARM_LAMP_PATTERN_OFF: + str_pat = "off" + elif pattern == self.TD_ALARM_LAMP_PATTERN_OK: + str_pat = "ok" + elif pattern == self.TD_ALARM_LAMP_PATTERN_FAULT: + str_pat = "fault" + elif pattern == self.TD_ALARM_LAMP_PATTERN_HIGH: + str_pat = "high" + elif pattern == self.TD_ALARM_LAMP_PATTERN_MEDIUM: + str_pat = "medium" + elif pattern == self.TD_ALARM_LAMP_PATTERN_LOW: + str_pat = "low" + else: + str_pat = "manual" + self.logger.debug("Alarm lamp pattern overridden to " + str_pat + ":" + + str(received_message['message'][DenaliMessage.PAYLOAD_START_INDEX])) + # response payload is OK or not OK + return received_message['message'][DenaliMessage.PAYLOAD_START_INDEX] + else: + self.logger.debug("Timeout!!!!") + return False + + def cmd_alarm_info_broadcast_interval_override(self, ms: int = 1000, reset: int = NO_RESET): + """ + Constructs and sends the alarm information broadcast interval override command + Constraints: + Must be logged into TD. + Given interval must be non-zero and a multiple of the TD general task interval (50 ms). + + @param ms: integer - interval (in ms) to override with + @param reset: integer - 1 to reset a previous override, 0 to override + @return: 1 if successful, zero otherwise + """ + + if not check_broadcast_interval_override_ms(ms): + return False + + rst = integer_to_bytearray(reset) + mis = integer_to_bytearray(ms) + payload = rst + mis + + message = DenaliMessage.build_message(channel_id=DenaliChannels.dialin_to_td_ch_id, + message_id=MsgIds.MSG_ID_TD_ALARM_INFO_PUBLISH_INTERVAL_OVERRIDE_REQUEST.value, + payload=payload) + + self.logger.debug("override alarm information broadcast interval") + + # Send message + received_message = self.can_interface.send(message) + + # If there is content... + if received_message is not None: + if reset == RESET: + str_res = "reset back to normal: " + else: + str_res = str(ms) + " ms: " + self.logger.debug("Alarm information broadcast interval overridden to " + str_res + + str(received_message['message'][DenaliMessage.PAYLOAD_START_INDEX])) + # response payload is OK or not OK + return received_message['message'][DenaliMessage.PAYLOAD_START_INDEX] + else: + self.logger.debug("Timeout!!!!") + return False + + def cmd_alarm_status_broadcast_interval_override(self, ms: int = 250, reset: int = NO_RESET): + """ + Constructs and sends the alarm status broadcast interval override command + Constraints: + Must be logged into TD. + Given interval must be non-zero and a multiple of the TD general task interval (50 ms). + + @param ms: integer - interval (in ms) to override with + @param reset: integer - 1 to reset a previous override, 0 to override + @return: 1 if successful, zero otherwise + """ + + if not check_broadcast_interval_override_ms(ms): + return False + + rst = integer_to_bytearray(reset) + mis = integer_to_bytearray(ms) + payload = rst + mis + + message = DenaliMessage.build_message(channel_id=DenaliChannels.dialin_to_td_ch_id, + message_id=MsgIds.MSG_ID_TD_ALARM_STATUS_PUBLISH_INTERVAL_OVERRIDE_REQUEST.value, + payload=payload) + + self.logger.debug("override alarm status broadcast interval") + + # Send message + received_message = self.can_interface.send(message) + + # If there is content... + if received_message is not None: + if reset == RESET: + str_res = "reset back to normal: " + else: + str_res = str(ms) + " ms: " + self.logger.debug("Alarm status broadcast interval overridden to " + str_res + + str(received_message['message'][DenaliMessage.PAYLOAD_START_INDEX])) + # response payload is OK or not OK + return received_message['message'][DenaliMessage.PAYLOAD_START_INDEX] + else: + self.logger.debug("Timeout!!!!") + return False + + def cmd_alarm_audio_level_override(self, volume: int = 5, reset: int = NO_RESET): + """ + Constructs and sends the alarm audio volume override command + Constraints: + Must be logged into TD. + Given volume must be an integer between 1 and 5. + + @param volume: integer - alarm audio volume level (1..5) + @param reset: integer - 1 to reset a previous override, 0 to override + @return: 1 if successful, zero otherwise + """ + + rst = integer_to_bytearray(reset) + vol = integer_to_bytearray(volume) + payload = rst + vol + + message = DenaliMessage.build_message(channel_id=DenaliChannels.dialin_to_td_ch_id, + message_id=MsgIds.MSG_ID_TD_ALARM_AUDIO_LEVEL_OVERRIDE_REQUEST.value, + payload=payload) + + self.logger.debug("override alarm audio volume level") + + # Send message + received_message = self.can_interface.send(message) + + # If there is content... + if received_message is not None: + if reset == RESET: + str_res = "reset back to normal: " + else: + str_res = str(volume) + ": " + self.logger.debug("Alarm audio volume level overridden to " + str_res + + str(received_message['message'][DenaliMessage.PAYLOAD_START_INDEX])) + # response payload is OK or not OK + return received_message['message'][DenaliMessage.PAYLOAD_START_INDEX] + else: + self.logger.debug("Timeout!!!!") + return False + + def cmd_alarm_audio_current_hg_override(self, current: float = 0.0, reset: int = NO_RESET): + """ + Constructs and sends the alarm audio current (high gain) override command + Constraints: + Must be logged into TD. + + @param current: float - current (in mA) for high gain alarm audio + @param reset: integer - 1 to reset a previous override, 0 to override + @return: 1 if successful, zero otherwise + """ + + rst = integer_to_bytearray(reset) + cur = float_to_bytearray(current) + payload = rst + cur + + message = DenaliMessage.build_message(channel_id=DenaliChannels.dialin_to_td_ch_id, + message_id=MsgIds.MSG_ID_TD_ALARM_AUDIO_CURRENT_HG_OVERRIDE_REQUEST.value, + payload=payload) + + self.logger.debug("override alarm audio high gain current") + + # Send message + received_message = self.can_interface.send(message) + + # If there is content... + if received_message is not None: + if reset == RESET: + str_res = "reset back to normal: " + else: + str_res = str(current) + " mA: " + self.logger.debug("Alarm audio high gain current overridden to " + str_res + + str(received_message['message'][DenaliMessage.PAYLOAD_START_INDEX])) + # response payload is OK or not OK + return received_message['message'][DenaliMessage.PAYLOAD_START_INDEX] + else: + self.logger.debug("Timeout!!!!") + return False + + def cmd_alarm_audio_current_lg_override(self, current: float = 0.0, reset: int = NO_RESET): + """ + Constructs and sends the alarm audio current (low gain) override command + Constraints: + Must be logged into TD. + + @param current: float - current (in mA) for low gain alarm audio + @param reset: integer - 1 to reset a previous override, 0 to override + @return: 1 if successful, zero otherwise + """ + + rst = integer_to_bytearray(reset) + cur = float_to_bytearray(current) + payload = rst + cur + + message = DenaliMessage.build_message(channel_id=DenaliChannels.dialin_to_td_ch_id, + message_id=MsgIds.MSG_ID_TD_ALARM_AUDIO_CURRENT_LG_OVERRIDE_REQUEST.value, + payload=payload) + + self.logger.debug("override alarm audio high gain current") + + # Send message + received_message = self.can_interface.send(message) + + # If there is content... + if received_message is not None: + if reset == RESET: + str_res = "reset back to normal: " + else: + str_res = str(current) + " mA: " + self.logger.debug("Alarm audio low gain current overridden to " + str_res + + str(received_message['message'][DenaliMessage.PAYLOAD_START_INDEX])) + # response payload is OK or not OK + return received_message['message'][DenaliMessage.PAYLOAD_START_INDEX] + else: + self.logger.debug("Timeout!!!!") + return False + + def cmd_alarm_backup_audio_current_override(self, current: float = 0.0, reset: int = NO_RESET): + """ + Constructs and sends the backup alarm audio current override command + Constraints: + Must be logged into TD. + + @param current: float - current (in mA) for backup alarm audio + @param reset: integer - 1 to reset a previous override, 0 to override + @return: 1 if successful, zero otherwise + """ + + rst = integer_to_bytearray(reset) + cur = float_to_bytearray(current) + payload = rst + cur + + message = DenaliMessage.build_message(channel_id=DenaliChannels.dialin_to_td_ch_id, + message_id=MsgIds.MSG_ID_TD_BACKUP_ALARM_AUDIO_CURRENT_OVERRIDE_REQUEST.value, + payload=payload) + + self.logger.debug("override alarm backup audio current") + + # Send message + received_message = self.can_interface.send(message) + + # If there is content... + if received_message is not None: + if reset == RESET: + str_res = "reset back to normal: " + else: + str_res = str(current) + " mA: " + self.logger.debug("Alarm backup audio current overridden to " + str_res + + str(received_message['message'][DenaliMessage.PAYLOAD_START_INDEX])) + # response payload is OK or not OK + return received_message['message'][DenaliMessage.PAYLOAD_START_INDEX] + else: + self.logger.debug("Timeout!!!!") + return False Index: leahi_dialin/td/modules/blood_flow.py =================================================================== diff -u --- leahi_dialin/td/modules/blood_flow.py (revision 0) +++ leahi_dialin/td/modules/blood_flow.py (revision 2138d06d100fdcf23f2e9069f35ee2fdee62008f) @@ -0,0 +1,390 @@ +########################################################################### +# +# 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 blood_flow.py +# +# @author (last) Micahel Garthwaite +# @date (last) 17-Aug-2023 +# @author (original) Peter Lucia +# @date (original) 02-Apr-2020 +# +############################################################################ +import struct +from logging import Logger + +from leahi_dialin.td.constants import PUMP_CONTROL_MODE_CLOSED_LOOP, PUMP_CONTROL_MODE_OPEN_LOOP +from leahi_dialin.td.constants import RESET, NO_RESET +from leahi_dialin.common.msg_defs import MsgIds, MsgFieldPositions +from leahi_dialin.protocols.CAN import DenaliMessage, DenaliChannels +from leahi_dialin.utils.base import AbstractSubSystem, publish +from leahi_dialin.utils.checks import check_broadcast_interval_override_ms +from leahi_dialin.utils.conversions import integer_to_bytearray, float_to_bytearray + + +class TDBloodFlow(AbstractSubSystem): + """ + Treatment Device (TD) Dialin API sub-class for blood-flow related commands. + """ + + def __init__(self, can_interface, logger: Logger): + """ + TDBloodFlow constructor + """ + super().__init__() + self.can_interface = can_interface + self.logger = logger + + if self.can_interface is not None: + channel_id = DenaliChannels.td_sync_broadcast_ch_id + msg_id = MsgIds.MSG_ID_TD_BLOOD_PUMP_DATA.value + self.can_interface.register_receiving_publication_function(channel_id, msg_id, + self._handler_blood_flow_sync) + self.td_blood_flow_timestamp = 0.0 + self.set_blood_flow_rate = 0 + self.measured_blood_flow_rate = 0.0 + self.measured_blood_pump_rotor_speed = 0.0 + self.measured_blood_pump_speed = 0.0 + self.measured_blood_pump_motor_speed = 0.0 + self.measured_blood_pump_motor_current = 0.0 + self.rotor_count = 0 + self.pres_blood_flow_rate = 0 + self.rotor_hall_state = 0 + + + @publish(["td_blood_flow_timestamp", "target_blood_flow_rate", "measured_blood_flow_rate", "measured_blood_pump_rotor_speed", + "measured_blood_pump_speed", "measured_blood_pump_mc_speed", "measured_blood_pump_mc_current", + "rotor_count", "pres_blood_flow_rate", "rotor_hall_state"]) + def _handler_blood_flow_sync(self, message, timestamp=0.0): + """ + Handles published blood flow data messages. Blood flow data are captured + for reference. + + @param message: published blood flow data message + @return: none + """ + + tgt = struct.unpack('i', bytearray( + message['message'][MsgFieldPositions.START_POS_FIELD_1:MsgFieldPositions.END_POS_FIELD_1])) + flow = struct.unpack('f', bytearray( + message['message'][MsgFieldPositions.START_POS_FIELD_2:MsgFieldPositions.END_POS_FIELD_2])) + rotor = struct.unpack('f', bytearray( + message['message'][MsgFieldPositions.START_POS_FIELD_3:MsgFieldPositions.END_POS_FIELD_3])) + speed = struct.unpack('f', bytearray( + message['message'][MsgFieldPositions.START_POS_FIELD_4:MsgFieldPositions.END_POS_FIELD_4])) + mcspeed = struct.unpack('f', bytearray( + message['message'][MsgFieldPositions.START_POS_FIELD_5:MsgFieldPositions.END_POS_FIELD_5])) + mccurr = struct.unpack('f', bytearray( + message['message'][MsgFieldPositions.START_POS_FIELD_6:MsgFieldPositions.END_POS_FIELD_6])) + rot = struct.unpack('I', bytearray( + message['message'][MsgFieldPositions.START_POS_FIELD_8:MsgFieldPositions.END_POS_FIELD_7])) + pres = struct.unpack('I', bytearray( + message['message'][MsgFieldPositions.START_POS_FIELD_9:MsgFieldPositions.END_POS_FIELD_8])) + hal = struct.unpack('I', bytearray( + message['message'][MsgFieldPositions.START_POS_FIELD_10:MsgFieldPositions.END_POS_FIELD_9])) + + self.target_blood_flow_rate = tgt[0] + self.measured_blood_flow_rate = flow[0] + self.measured_blood_pump_rotor_speed = rotor[0] + self.measured_blood_pump_speed = speed[0] + self.measured_blood_pump_motor_speed = mcspeed[0] + self.measured_blood_pump_motor_current = mccurr[0] + self.rotor_count = rot[0] + self.pres_blood_flow_rate = pres[0] + self.rotor_hall_state = hal[0] + self.td_blood_flow_timestamp = timestamp + + def cmd_blood_flow_set_flow_rate_request(self, flow: int, mode: int = PUMP_CONTROL_MODE_CLOSED_LOOP) -> int: + """ + Constructs and sends the blood flow set point command + Constraints: + Must be logged into TD. + + @param flow: integer - flow set point (in mL/min) (negative for reverse direction) + @param mode: integer - 0 for closed loop control mode or 1 for open loop control mode + @return: 1 if successful, zero otherwise + """ + + flo = integer_to_bytearray(flow) + mod = integer_to_bytearray(mode) + payload = flo + mod + + message = DenaliMessage.build_message(channel_id=DenaliChannels.dialin_to_td_ch_id, + message_id=MsgIds.MSG_ID_TD_BLOOD_PUMP_SET_FLOW_RATE_REQUEST.value, + payload=payload) + + self.logger.debug("override blood flow set point") + + # Send message + received_message = self.can_interface.send(message) + + # If there is content... + if received_message is not None: + # self.logger.debug(received_message) + if reset == RESET: + str_res = "reset back to normal" + else: + str_res = str(flow) + + if mode == PUMP_CONTROL_MODE_OPEN_LOOP: + str_mode = " (open loop): " + else: + str_mode = " (closed loop): " + self.logger.debug( + "Blood flow set point overridden to " + str_res + " mL/min" + str_mode + + + str(received_message['message'][DenaliMessage.PAYLOAD_START_INDEX])) + # response payload is OK or not OK + return received_message['message'][DenaliMessage.PAYLOAD_START_INDEX] + else: + self.logger.debug("Timeout!!!!") + return False + + def cmd_blood_flow_set_speed_rate_request(self, rpm: int) -> int: + """ + Constructs and sends the blood flow set point command + Constraints: + Must be logged into TD. + + @param rpm: integer - speed set point (in rpm) + @return: 1 if successful, zero otherwise + """ + + rpm = integer_to_bytearray(rpm) + payload = rpm + + message = DenaliMessage.build_message(channel_id=DenaliChannels.dialin_to_td_ch_id, + message_id=MsgIds.MSG_ID_TD_BLOOD_PUMP_SET_SPEED_REQUEST.value, + payload=payload) + + self.logger.debug("override blood flow set point") + + # Send message + received_message = self.can_interface.send(message) + + # If there is content... + if received_message is not None: + # self.logger.debug(received_message) + if reset == RESET: + str_res = "reset back to normal" + else: + str_res = str(flow) + + if mode == PUMP_CONTROL_MODE_OPEN_LOOP: + str_mode = " (open loop): " + else: + str_mode = " (closed loop): " + self.logger.debug( + "Blood flow set point overridden to " + str_res + " mL/min" + str_mode + + + str(received_message['message'][DenaliMessage.PAYLOAD_START_INDEX])) + # response payload is OK or not OK + return received_message['message'][DenaliMessage.PAYLOAD_START_INDEX] + else: + self.logger.debug("Timeout!!!!") + return False + + def cmd_blood_flow_measured_override(self, flow: int, reset: int = NO_RESET) -> int: + """ + Constructs and sends the measured blood flow override command + Constraints: + Must be logged into TD. + + @param flow: integer - measured flow (in mL/min) to override with + @param reset: integer - 1 to reset a previous override, 0 to override + @return: 1 if successful, zero otherwise + """ + + rst = integer_to_bytearray(reset) + flo = float_to_bytearray(flow) + payload = rst + flo + + message = DenaliMessage.build_message(channel_id=DenaliChannels.dialin_to_td_ch_id, + message_id=MsgIds.MSG_ID_TD_BLOOD_PUMP_MEASURED_FLOW_RATE_OVERRIDE_REQUEST.value, + payload=payload) + + self.logger.debug("override measured blood flow") + + # Send message + received_message = self.can_interface.send(message) + + # If there is content... + if received_message is not None: + # self.logger.debug(received_message) + if reset == RESET: + str_res = "reset back to normal" + else: + str_res = str(flow) + self.logger.debug("Blood flow (measured)) overridden to " + str_res + " mL/min: " + + str(received_message['message'][DenaliMessage.PAYLOAD_START_INDEX])) + # response payload is OK or not OK + return received_message['message'][DenaliMessage.PAYLOAD_START_INDEX] + else: + self.logger.debug("Timeout!!!!") + return False + + def cmd_blood_pump_measured_motor_speed_override(self, speed: int, reset: int = NO_RESET) -> int: + """ + Constructs and sends the measured blood pump motor speed override \n + command. + Constraints: + Must be logged into TD. + + @param speed: integer - speed (in RPM) to override with + @param reset: integer - 1 to reset a previous override, 0 to override + @return: 1 if successful, zero otherwise + """ + + rst = integer_to_bytearray(reset) + spd = float_to_bytearray(speed) + payload = rst + spd + + message = DenaliMessage.build_message(channel_id=DenaliChannels.dialin_to_td_ch_id, + message_id=MsgIds.MSG_ID_TD_BLOOD_PUMP_MEASURED_MOTOR_SPEED_OVERRIDE_REQUEST.value, + payload=payload) + + self.logger.debug("override measured blood pump speed") + + # Send message + received_message = self.can_interface.send(message) + + # If there is content... + if received_message is not None: + # self.logger.debug(received_message) + if reset == RESET: + str_res = "reset back to normal" + else: + str_res = str(speed) + self.logger.debug("Blood pump speed (measured) overridden to " + str_res + " RPM: " + + str(received_message['message'][DenaliMessage.PAYLOAD_START_INDEX])) + # response payload is OK or not OK + return received_message['message'][DenaliMessage.PAYLOAD_START_INDEX] + else: + self.logger.debug("Timeout!!!!") + return False + + def cmd_blood_pump_measured_rotor_speed_override(self, speed: int, reset: int = NO_RESET) -> int: + """ + Constructs and sends the measured blood pump rotor speed override \n + command. + Constraints: + Must be logged into TD. + + @param speed: integer - speed (in RPM) to override with + @param reset: integer - 1 to reset a previous override, 0 to override + @return: 1 if successful, zero otherwise + """ + + rst = integer_to_bytearray(reset) + spd = float_to_bytearray(speed) + payload = rst + spd + + message = DenaliMessage.build_message(channel_id=DenaliChannels.dialin_to_td_ch_id, + message_id=MsgIds.MSG_ID_TD_BLOOD_PUMP_MEASURED_ROTOR_SPEED_OVERRIDE_REQUEST.value, + payload=payload) + + self.logger.debug("override measured blood pump rotor speed") + + # Send message + received_message = self.can_interface.send(message) + + # If there is contentleahi_dialin.. + if received_message is not None: + # self.logger.debug(received_message) + if reset == RESET: + str_res = "reset back to normal" + else: + str_res = str(speed) + self.logger.debug("Blood pump rotor speed (measured) overridden to " + str_res + " RPM: " + + str(received_message['message'][DenaliMessage.PAYLOAD_START_INDEX])) + # response payload is OK or not OK + return received_message['message'][DenaliMessage.PAYLOAD_START_INDEX] + else: + self.logger.debug("Timeout!!!!") + return False + + def cmd_blood_flow_broadcast_interval_override(self, ms: int, reset: int = NO_RESET) -> int: + """ + Constructs and sends the measured blood flow broadcast interval override command + Constraints: + Must be logged into TD. + Given interval must be non-zero and a multiple of the TD general task interval (50 ms). + + @param ms: integer - interval (in ms) to override with + @param reset: integer - 1 to reset a previous override, 0 to override + @return: 1 if successful, zero otherwise + """ + + if not check_broadcast_interval_override_ms(ms): + return False + + rst = integer_to_bytearray(reset) + mis = integer_to_bytearray(ms) + payload = rst + mis + + message = DenaliMessage.build_message(channel_id=DenaliChannels.dialin_to_td_ch_id, + message_id=MsgIds.MSG_ID_TD_BLOOD_PUMP_PUBLISH_INTERVAL_OVERRIDE_REQUEST.value, + payload=payload) + + self.logger.debug("override blood flow broadcast interval") + + # Send message + received_message = self.can_interface.send(message) + + # If there is content... + if received_message is not None: + # self.logger.debug(received_message) + if reset == RESET: + str_res = "reset back to normal: " + else: + str_res = str(ms) + " ms: " + self.logger.debug("Blood flow broadcast interval overridden to " + str_res + + str(received_message['message'][DenaliMessage.PAYLOAD_START_INDEX])) + # response payload is OK or not OK + return received_message['message'][DenaliMessage.PAYLOAD_START_INDEX] + else: + self.logger.debug("Timeout!!!!") + return False + + def cmd_blood_pump_rotor_count_override(self, rot_count: int, reset: int = NO_RESET) -> int: + """ + Constructs and sends the blood pump rotor count override command + Constraints: + Must be logged into TD. + Given count must be zero or positive integer. + + @param rot_count: integer - rotor count to override with + @param reset: integer - 1 to reset a previous override, 0 to override + @return: 1 if successful, zero otherwise + """ + + rst = integer_to_bytearray(reset) + cnt = integer_to_bytearray(rot_count) + payload = rst + cnt + + message = DenaliMessage.build_message(channel_id=DenaliChannels.dialin_to_td_ch_id, + message_id=MsgIds.MSG_ID_TD_BLOOD_PUMP_ROTOR_COUNT_OVERRIDE_REQUEST.value, + payload=payload) + + self.logger.debug("override blood pump rotor count") + + # Send message + received_message = self.can_interface.send(message) + + # If there is content... + if received_message is not None: + # self.logger.debug(received_message) + if reset == RESET: + str_res = "reset back to normal: " + else: + str_res = str(rot_count) + ": " + self.logger.debug("Blood pump rotor count overridden to " + str_res + + str(received_message['message'][DenaliMessage.PAYLOAD_START_INDEX])) + # response payload is OK or not OK + return received_message['message'][DenaliMessage.PAYLOAD_START_INDEX] + else: + self.logger.debug("Timeout!!!!") + return False Index: leahi_dialin/td/modules/bubble_detector.py =================================================================== diff -u --- leahi_dialin/td/modules/bubble_detector.py (revision 0) +++ leahi_dialin/td/modules/bubble_detector.py (revision 2138d06d100fdcf23f2e9069f35ee2fdee62008f) @@ -0,0 +1,170 @@ +########################################################################### +# +# Copyright (c) 2021-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 air_bubbles.py +# +# @author (last) Sean Nash +# @date (last) 04-May-2023 +# @author (original) Peman Montazemi +# @date (original) 18-May-2021 +# +############################################################################ +import struct +from logging import Logger + +from leahi_dialin.td.constants import RESET, NO_RESET +from leahi_dialin.common.msg_defs import MsgIds, MsgFieldPositions +from leahi_dialin.protocols.CAN import DenaliMessage, DenaliChannels +from leahi_dialin.utils.base import AbstractSubSystem, publish +from leahi_dialin.utils.conversions import integer_to_bytearray + + +class TDBubbleDectector(AbstractSubSystem): + """ + TDAirBubbles + + Treatment Delivery (TD) Dialin API sub-class for air bubbles related commands. + ADA: Air bubble Detector Arterial + ADV: Air bubble Detector Venous + """ + + # Air bubble detectors + ADV = 0 # Air bubble Detector Venous + + # Air bubble detectors status + BUBBLE_DETECTED_STATUS = 0 # Air bubble detected + FLUID_DETECTED_STATUS = 1 # Fluid (no air bubble) detected + + # Air bubble detectors state machine states + AIR_BUBBLE_NORMAL_STATE = 0 # Normal state + AIR_BUBBLE_SELF_TEST_STATE = 1 # Self-test state + + def __init__(self, can_interface, logger: Logger): + """ + + @param can_interface: Denali Can Messenger object + """ + super().__init__() + self.can_interface = can_interface + self.logger = logger + + if self.can_interface is not None: + channel_id = DenaliChannels.td_sync_broadcast_ch_id + msg_id = MsgIds.MSG_ID_TD_BUBBLES_DATA.value + self.can_interface.register_receiving_publication_function(channel_id, msg_id, + self._handler_air_bubbles_data_sync) + + self.td_air_bubbles_timestamp = 0.0 + # Initialize status of ADV air bubble detectors to fluid (no air bubble) detected + self.air_bubbles_status = [self.FLUID_DETECTED_STATUS] + + # Initialize state of ADV air bubble detectors state machine to normal + self.air_bubbles_state = [self.AIR_BUBBLE_NORMAL_STATE] + + @publish(["td_air_bubbles_timestamp", "air_bubbles_status", "air_bubbles_state"]) + def _handler_air_bubbles_data_sync(self, message, timestamp=0.0): + """ + Handles published air bubbles data messages. Air bubble status and state are captured + for ADV detector. + + @param message: published air bubbles data message as: ADV status, ADV state + @return: None + """ + + adv_status = struct.unpack('i', bytearray( + message['message'][MsgFieldPositions.START_POS_FIELD_1:MsgFieldPositions.END_POS_FIELD_1])) + adv_state = struct.unpack('i', bytearray( + message['message'][MsgFieldPositions.START_POS_FIELD_2:MsgFieldPositions.END_POS_FIELD_2])) + + self.air_bubbles_status = [adv_status[0]] + self.air_bubbles_state = [adv_state[0]] + self.td_air_bubbles_timestamp = timestamp + + def cmd_air_bubble_status_override(self, status: int, index: int, reset: int = NO_RESET) -> int: + """ + Constructs and sends the air bubble detector status override command + Constraints: + Must be logged into TD. + Given detector must be one of the detectors listed below. + + @param status: unsigned int - status (0=air bubble, 1=fluid) to override detector with + @param index: integer - 0 for ADV status override + @param reset: integer - 1 to reset a previous override, 0 to override + @return: 1 if successful, zero otherwise + """ + + rst = integer_to_bytearray(reset) + i = integer_to_bytearray(index) + stat = integer_to_bytearray(status) + payload = rst + stat + i + + message = DenaliMessage.build_message(channel_id=DenaliChannels.dialin_to_td_ch_id, + message_id=MsgIds.MSG_ID_TD_BUBBLE_OVERRIDE_REQUEST.value, + payload=payload) + + if index == self.ADV: + self.logger.debug("Override air bubble detector ADV status value") + + # 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.debug("Timeout!!!!") + return False + + def cmd_air_bubbles_data_broadcast_interval_override(self, ms: int, reset: int = NO_RESET) -> int: + """ + Constructs and sends the air bubbles data broadcast interval override command + Constraints: + Must be logged into TD. + Given interval must be positive non-zero and a multiple of the TD priority task interval (50 ms). + + @param ms: integer - interval (in ms) to override with + @param reset: integer - 1 to reset a previous override, 0 to override + @return: 1 if successful, zero otherwise + """ + + if ms > 0 and ms % 50 == 0: + rst = integer_to_bytearray(reset) + mis = integer_to_bytearray(ms) + payload = rst + mis + + message = DenaliMessage.build_message(channel_id=DenaliChannels.dialin_to_td_ch_id, + message_id=MsgIds.MSG_ID_TD_BUBBLE_PUBLISH_INTERVAL_OVERRIDE_REQUEST.value, + payload=payload) + + self.logger.debug("Override TD air bubbles data broadcast interval to"+str(ms)+"ms") + + # Send message + received_message = self.can_interface.send(message) + + # If there is content... + if received_message is not None: + if reset == RESET: + str_res = "reset back to normal: " + else: + str_res = str(ms) + " ms: " + self.logger.debug("Air bubbles data broadcast interval overridden to " + str_res + + str(received_message['message'][DenaliMessage.PAYLOAD_START_INDEX])) + # response payload is OK or not OK + return received_message['message'][DenaliMessage.PAYLOAD_START_INDEX] + else: + self.logger.debug("Timeout!!!!") + return False + elif ms <= 0: + self.logger.debug("ms must be positive non-zero.") + return False + elif ms % 50 != 0: + self.logger.debug("ms must be a multiple of 50.") + return False + else: + self.logger.debug("ms must be an integer.") + return False \ No newline at end of file Index: leahi_dialin/td/modules/buttons.py =================================================================== diff -u --- leahi_dialin/td/modules/buttons.py (revision 0) +++ leahi_dialin/td/modules/buttons.py (revision 2138d06d100fdcf23f2e9069f35ee2fdee62008f) @@ -0,0 +1,157 @@ +########################################################################### +# +# 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 buttons.py +# +# @author (last) Micahel Garthwaite +# @date (last) 05-Nov-2024 +# @author (original) Peter Lucia +# @date (original) 14-Oct-2024 +# +############################################################################ +import struct +from logging import Logger + +from leahi_dialin.td.constants import RESET, NO_RESET +from leahi_dialin.common.msg_defs import MsgIds, MsgFieldPositions +from leahi_dialin.protocols.CAN import DenaliMessage, DenaliCanMessenger, DenaliChannels +from leahi_dialin.utils.base import AbstractSubSystem, publish +from leahi_dialin.utils.conversions import integer_to_bytearray + + +class TDButtons(AbstractSubSystem): + """ + Treatment Delivery (TD) Dialin API sub-class for button related commands. + """ + + def __init__(self, can_interface: DenaliCanMessenger, logger: Logger): + """ + TD_Buttons constructor + + @param can_interface: the Denali CAN interface object + """ + + super().__init__() + self.can_interface = can_interface + self.logger = logger + self.poweroff_timeout_expired = False + + if self.can_interface is not None: + self.can_interface.register_receiving_publication_function(DenaliChannels.td_to_ui_ch_id, + MsgIds.MSG_ID_OFF_BUTTON_PRESS_REQUEST.value, + self._handler_poweroff_timeout_occurred) + self.td_power_off_timestamp = 0.0 + + def reset_poweroff_timeout_expired(self): + """ + Resets the dialin poweroff timeout flag to False + + @return: None + """ + + self.poweroff_timeout_expired = False + + @publish(['td_power_off_timestamp', "poweroff_timeout_expired"]) + def _handler_poweroff_timeout_occurred(self, message, timestamp=0.0): + """ + Poweroff timeout occurred handler + + @param message: The poweroff timeout occurred message string + @return: None + """ + if len(message["message"]) < 16: + self.logger.debug("Poweroff message id detected, but was the wrong length.") + return + + mode = struct.unpack('h', bytearray( + message["message"][MsgFieldPositions.START_POS_FIELD_1: MsgFieldPositions.START_POS_FIELD_1 + 2])) + + if len(mode) > 0: + self.poweroff_timeout_expired = bool(mode[0]) + self.td_power_off_timestamp = timestamp + + def cmd_off_button_override(self, state: int, reset: int = NO_RESET) -> int: + """ + Constructs and sends the Off button override command + See TD/App/Controllers/Buttons.c + Constraints: + Must be logged into TD. + Given state must be a 0 or 1. + + @param state: integer - 1 to override off button to "pressed", 0 to "released" + @param reset: integer - 1 to reset a previous override, 0 to override + @return: 1 if successful, zero otherwise + """ + + rst = integer_to_bytearray(reset) + sta = integer_to_bytearray(state) + payload = rst + sta + + message = DenaliMessage.build_message(channel_id=DenaliChannels.dialin_to_td_ch_id, + message_id=MsgIds.MSG_ID_TD_OFF_BUTTON_OVERRIDE_REQUEST.value, + payload=payload) + + self.logger.debug("override off button") + + # Send message + received_message = self.can_interface.send(message) + + # If there is content... + if received_message is not None: + if reset == RESET: + str_res = "reset back to normal" + else: + str_res = ("pressed" if state != 0 else "released") + + self.logger.debug("Off button overridden to " + str_res + ":" + + str(received_message['message'][DenaliMessage.PAYLOAD_START_INDEX])) + # response payload is OK or not OK + return received_message['message'][DenaliMessage.PAYLOAD_START_INDEX] + else: + self.logger.debug("Timeout!!!!") + return False + + def cmd_stop_button_override(self, state: int, reset: int = NO_RESET) -> int: + """ + Constructs and sends the Stop button override command + See TD/App/Controllers/Buttons.c + Constraints: + Must be logged into TD. + Given state must be a 0 or 1. + + @param state: integer - 1 to override stop button to "pressed", 0 to "released" + @param reset: integer - 1 to reset a previous override, 0 to override + @return: 1 if successful, zero otherwise + """ + + rst = integer_to_bytearray(reset) + sta = integer_to_bytearray(state) + payload = rst + sta + + message = DenaliMessage.build_message(channel_id=DenaliChannels.dialin_to_td_ch_id, + message_id=MsgIds.MSG_ID_TD_STOP_BUTTON_OVERRIDE_REQUEST.value, + payload=payload) + + self.logger.debug("override stop button") + + # Send message + received_message = self.can_interface.send(message) + + # If there is content... + if received_message is not None: + if reset == RESET: + str_res = "reset back to normal" + else: + str_res = ("pressed" if state != 0 else "released") + self.logger.debug("Stop button overridden to " + str_res + ":" + + str(received_message['message'][DenaliMessage.PAYLOAD_START_INDEX])) + + # response payload is OK or not OK + return received_message["message"][DenaliMessage.PAYLOAD_START_INDEX] + else: + self.logger.debug("Timeout!!!!") + return False Index: leahi_dialin/td/modules/pressure_sensors.py =================================================================== diff -u --- leahi_dialin/td/modules/pressure_sensors.py (revision 0) +++ leahi_dialin/td/modules/pressure_sensors.py (revision 2138d06d100fdcf23f2e9069f35ee2fdee62008f) @@ -0,0 +1,192 @@ +########################################################################### +# +# 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 pressure_occlusion.py +# +# @author (last) Vinayakam Mani +# @date (last) 02-May-2024 +# @author (original) Peter Lucia +# @date (original) 02-Apr-2020 +# +############################################################################ +import struct +from logging import Logger + +from leahi_dialin.td.constants import RESET, NO_RESET +from leahi_dialin.common.msg_defs import MsgIds, MsgFieldPositions +from leahi_dialin.protocols.CAN import DenaliMessage, DenaliChannels +from leahi_dialin.utils.base import AbstractSubSystem, publish +from leahi_dialin.utils.checks import check_broadcast_interval_override_ms +from leahi_dialin.utils.conversions import integer_to_bytearray, float_to_bytearray + + +class TDPressureSensors(AbstractSubSystem): + """ + Treatment Delivery (TD) Dialin API sub-class for pressure related commands. + """ + + def __init__(self, can_interface, logger: Logger): + """ + TDPressureOcclusion constructor + + """ + super().__init__() + self.can_interface = can_interface + self.logger = logger + + if self.can_interface is not None: + channel_id = DenaliChannels.td_sync_broadcast_ch_id + msg_id = MsgIds.MSG_ID_PRESSURE_DATA.value + self.can_interface.register_receiving_publication_function(channel_id, msg_id, + self._handler_pressure_sync) + + self.td_pressure_timestamp = 0.0 + self.arterial_pressure = 0.0 + self.venous_pressure = 0.0 + self.arterial_long_filtered_pressure = 0.0 + self.venous_long_filtered_pressure = 0.0 + self.pressure_limits_state = 0 + self.arterial_pressure_limit_min = 0 + self.arterial_pressure_limit_max = 0 + self.venous_pressure_limit_min = 0 + self.venous_pressure_limit_max = 0 + + @publish([ + "td_pressure_timestamp", + "arterial_pressure", + "venous_pressure", + "pressure_limits_state", + "arterial_pressure_limit_min", + "arterial_pressure_limit_max", + "venous_pressure_limit_min", + "venous_pressure_limit_max", + "arterial_long_filtered_pressure", + "venous_long_filtered_pressure", + ]) + def _handler_pressure_sync(self, message, timestamp=0.0): + """ + Handles published pressure & occlusion data messages. Pressure data are captured + for reference. + + @param message: published pressure & occlusion data message + @return: none + """ + + art = struct.unpack('f', bytearray( + message['message'][MsgFieldPositions.START_POS_FIELD_1:MsgFieldPositions.END_POS_FIELD_1])) + ven = struct.unpack('f', bytearray( + message['message'][MsgFieldPositions.START_POS_FIELD_2:MsgFieldPositions.END_POS_FIELD_2])) + pls = struct.unpack('i', bytearray( + message['message'][MsgFieldPositions.START_POS_FIELD_3:MsgFieldPositions.END_POS_FIELD_3])) + apl = struct.unpack('i', bytearray( + message['message'][MsgFieldPositions.START_POS_FIELD_4:MsgFieldPositions.END_POS_FIELD_4])) + apu = struct.unpack('i', bytearray( + message['message'][MsgFieldPositions.START_POS_FIELD_5:MsgFieldPositions.END_POS_FIELD_5])) + vpl = struct.unpack('i', bytearray( + message['message'][MsgFieldPositions.START_POS_FIELD_6:MsgFieldPositions.END_POS_FIELD_6])) + vpu = struct.unpack('i', bytearray( + message['message'][MsgFieldPositions.START_POS_FIELD_7:MsgFieldPositions.END_POS_FIELD_7])) + lfa = struct.unpack('f', bytearray( + message['message'][MsgFieldPositions.START_POS_FIELD_8:MsgFieldPositions.END_POS_FIELD_8])) + lfv = struct.unpack('f', bytearray( + message['message'][MsgFieldPositions.START_POS_FIELD_9:MsgFieldPositions.END_POS_FIELD_9])) + + self.arterial_pressure = art[0] + self.venous_pressure = ven[0] + self.pressure_limits_state = pls[0] + self.arterial_pressure_limit_min = apl[0] + self.arterial_pressure_limit_max = apu[0] + self.venous_pressure_limit_min = vpl[0] + self.venous_pressure_limit_max = vpu[0] + self.arterial_long_filtered_pressure = lfa[0] + self.venous_long_filtered_pressure = lfv[0] + self.td_pressure_timestamp = timestamp + + def cmd_pressure_sensor_override(self, pressure: float, sensor: int, reset: int = NO_RESET) -> int: + """ + Constructs and sends the measured arterial pressure override command + PRESSURE_SENSOR_ARTERIAL = 0 ///< Arterial blood line pressure sensor + PRESSURE_SENSOR_VENOUS = 1 ///< Vensous blood line pressure sensor + Constraints: + Must be logged into TD. + + @param pressure: float - measured arterial pressure (in mmHg) to override with + @param sensor: bool - switch between filtered override and raw override + @param reset: integer - 1 to reset a previous override, 0 to override + @return: 1 if successful, zero otherwise + """ + + rst = integer_to_bytearray(reset) + sen = integer_to_bytearray(sensor) + prs = float_to_bytearray(pressure) + payload = rst + prs + sen + + message = DenaliMessage.build_message(channel_id=DenaliChannels.dialin_to_TD_ch_id, + message_id=MsgIds.MSG_ID_TD_PRESSURE_OVERRIDE_REQUEST.value, + payload=payload) + + self.logger.debug("override pressure sensor") + + # Send message + received_message = self.can_interface.send(message) + + # If there is content... + if received_message is not None: + if reset == RESET: + str_res = "reset back to normal. " + else: + str_res = str(prs) + " mmHg. " + self.logger.debug("pressure (measured)) overridden to " + str_res + + str(received_message['message'][DenaliMessage.PAYLOAD_START_INDEX])) + # response payload is OK or not OK + return received_message['message'][DenaliMessage.PAYLOAD_START_INDEX] + else: + self.logger.debug("Timeout!!!!") + return False + + def cmd_pressure_sensors_broadcast_interval_override(self, ms: int, reset: int = NO_RESET) -> int: + """ + Constructs and sends the measured pressure sensors broadcast interval override command + Constraints: + Must be logged into TD. + Given interval must be non-zero and a multiple of the TD general task interval (50 ms). + + @param ms: integer - interval (in ms) to override with + @param reset: integer - 1 to reset a previous override, 0 to override + @return: 1 if successful, zero otherwise + """ + + if not check_broadcast_interval_override_ms(ms): + return False + + rst = integer_to_bytearray(reset) + mis = integer_to_bytearray(ms) + payload = rst + mis + + message = DenaliMessage.build_message(channel_id=DenaliChannels.dialin_to_td_ch_id, + message_id=MsgIds.MSG_ID_TD_PRESSURE_PUBLISH_INTERVAL_OVERRIDE_REQUEST.value, + payload=payload) + + self.logger.debug("override blood flow broadcast interval") + + # Send message + received_message = self.can_interface.send(message) + + # If there is content... + if received_message is not None: + # self.logger.debug(received_message) + if reset == RESET: + str_res = "reset back to normal: " + else: + str_res = str(ms) + " ms: " + self.logger.debug("Blood flow broadcast interval overridden to " + str_res + + str(received_message['message'][DenaliMessage.PAYLOAD_START_INDEX])) + # response payload is OK or not OK + return received_message['message'][DenaliMessage.PAYLOAD_START_INDEX] + else: + self.logger.debug("Timeout!!!!") + return False \ No newline at end of file Index: leahi_dialin/td/modules/switches.py =================================================================== diff -u --- leahi_dialin/td/modules/switches.py (revision 0) +++ leahi_dialin/td/modules/switches.py (revision 2138d06d100fdcf23f2e9069f35ee2fdee62008f) @@ -0,0 +1,157 @@ +########################################################################### +# +# Copyright (c) 2021-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 switches.py +# +# @author (last) Micahel Garthwaite +# @date (last) 03-Mar-2023 +# @author (original) Dara Navaei +# @date (original) 25-Jul-2021 +# +############################################################################ + +import struct +from enum import unique +from logging import Logger + +from leahi_dialin.utils.conversions import integer_to_bytearray +from leahi_dialin.common.msg_defs import MsgIds, MsgFieldPositions +from leahi_dialin.td.constants import RESET, NO_RESET +from leahi_dialin.protocols.CAN import DenaliMessage, DenaliChannels +from leahi_dialin.utils.base import AbstractSubSystem, publish, DialinEnum +from leahi_dialin.utils.checks import check_broadcast_interval_override_ms + + +@unique +class TDSwitchStatus(DialinEnum): + CLOSED = 0 + OPEN = 1 + + + +@unique +class TDSwitchesNames(DialinEnum): + FRONT_DOOR = 0 + NUM_OF_DOORS_AND_SWITCHES = 1 + + +class TDSwitches(AbstractSubSystem): + """ + @brief Treatment Device (TD) Dialin API sub-class for TD switches related commands. + """ + + def __init__(self, can_interface, logger: Logger): + """ + TDSwitches constructor + + """ + super().__init__() + + self.can_interface = can_interface + self.logger = logger + + if self.can_interface is not None: + channel_id = DenaliChannels.td_sync_broadcast_ch_id + msg_id = MsgIds.MSG_ID_TD_SWITCHES_DATA.value + self.can_interface.register_receiving_publication_function(channel_id, msg_id, + self._handler_switches_sync) + + self.td_switches_status = {TDSwitchesNames.FRONT_DOOR.name: TDSwitchStatus.CLOSED.value} + self.td_switches_timestamp = 0.0 + + @publish(["td_switches_timestamp", "td_switches_status"]) + def _handler_switches_sync(self, message, timestamp=0.0): + """ + Handles published TD switches data messages. Switches data are captured for reference. + + @param message: published switches data message + @return: none + """ + front_door = struct.unpack('i', bytearray( + message['message'][MsgFieldPositions.START_POS_FIELD_1:MsgFieldPositions.END_POS_FIELD_1]))[0] + + self.td_switches_status[TDSwitchesNames.FRONT_DOOR.name] = TDSwitchStatus(front_door).value + + def cmd_switch_status_override(self, switch: int, status: int, reset: int = NO_RESET) -> int: + """ + Constructs and sends the TD switch status override command + Constraints: + Must be logged into TD. + + @param switch: (int) switch ID that is status is overridden + @param status: (int) status that the switch will be overridden to + @param reset: (int) 1 to reset a previous override, 0 to override + @return: 1 if successful, zero otherwise + """ + reset_value = integer_to_bytearray(reset) + sw = integer_to_bytearray(switch) + st = integer_to_bytearray(status) + payload = reset_value + st + sw + + message = DenaliMessage.build_message(channel_id=DenaliChannels.dialin_to_td_ch_id, + message_id=MsgIds.MSG_ID_TD_SWITCH_STATE_OVERRIDE_REQUEST.value, + payload=payload) + + self.logger.debug("Override switch status") + + # Send message + received_message = self.can_interface.send(message) + + # If there is no content... + if received_message is not None: + + self.logger.debug("Switch " + str(TDSwitchesNames(switch).name) + " to: " + + str(received_message['message'][DenaliMessage.PAYLOAD_START_INDEX])) + # response payload is OK or not OK + return received_message['message'][DenaliMessage.PAYLOAD_START_INDEX] + else: + self.logger.debug("Timeout!!!!") + return False + + def cmd_switches_data_broadcast_interval_override(self, ms: int, reset: int = NO_RESET) -> int: + """ + Constructs and sends the TD switch data publication override command. + Constraints: + Must be logged into TD. + Given interval must be non-zero and a multiple of the TD general task interval (50 ms). + + @param ms: (int) interval (in ms) to override with + @param reset: (int) 1 to reset a previous override, 0 to override + @return: 1 if successful, zero otherwise + """ + + if not check_broadcast_interval_override_ms(ms): + return False + + rst = integer_to_bytearray(reset) + mis = integer_to_bytearray(ms) + payload = rst + mis + + message = DenaliMessage.build_message(channel_id=DenaliChannels.dialin_to_td_ch_id, + message_id=MsgIds.MSG_ID_TD_SWITCHES_PUBLISH_INTERVAL_OVERRIDE_REQUEST.value, + payload=payload) + + self.logger.debug("Override TD switches data broadcast interval") + + # Send message + received_message = self.can_interface.send(message) + + # If there is content... + if received_message is not None: + # self.logger.debug(received_message) + if reset == RESET: + str_res = "reset back to normal" + else: + str_res = str(mis) + self.logger.debug( + "TD Switches data broadcast interval overridden to " + str_res + " ms: " + + str(received_message['message'][DenaliMessage.PAYLOAD_START_INDEX])) + # response payload is OK or not OK + return received_message['message'][DenaliMessage.PAYLOAD_START_INDEX] + else: + self.logger.debug("Timeout!!!!") + return False Index: leahi_dialin/td/modules/valves.py =================================================================== diff -u --- leahi_dialin/td/modules/valves.py (revision 0) +++ leahi_dialin/td/modules/valves.py (revision 2138d06d100fdcf23f2e9069f35ee2fdee62008f) @@ -0,0 +1,302 @@ +########################################################################### +# +# 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 valves.py +# +# @author (last) James Walter Taylor +# @date (last) 03-Aug-2023 +# @author (original) Dara Navaei +# @date (original) 19-Aug-2020 +# +############################################################################ + + +import struct +from enum import unique +from logging import Logger + +from leahi_dialin.td.constants import NO_RESET +from leahi_dialin.common import MsgIds +from leahi_dialin.protocols.CAN import DenaliMessage, DenaliChannels +from leahi_dialin.utils.base import AbstractSubSystem, publish, DialinEnum +from leahi_dialin.utils.checks import check_broadcast_interval_override_ms +from leahi_dialin.utils.conversions import integer_to_bytearray + + +@unique +class ValvesEnum(DialinEnum): + VBA = 0 + VBV = 1 + + +@unique +class ValvesPositions(DialinEnum): + VALVE_POSITION_NOT_IN_POSITION = 0 + VALVE_POSITION_A_INSERT_EJECT = 1 + VALVE_POSITION_B_OPEN = 2 + VALVE_POSITION_C_CLOSE = 3 + + +@unique +class ValvesStates(DialinEnum): + VALVE_STATE_WAIT_FOR_POST = 0 + VALVE_STATE_HOMING_NOT_STARTED = 1 + VALVE_STATE_HOMING_FIND_ENERGIZED_EDGE = 2 + VALVE_STATE_HOMING_FIND_DEENERGIZED_EDGE = 3 + VALVE_STATE_IDLE = 4 + VALVE_STATE_IN_TRANSITION = 5 + +class TDValves(AbstractSubSystem): + """ + Hemodialysis Device (HD) Dialin API sub-class for valves related commands. + """ + # Valves states publish message field positions + # Note the MsgFieldPosition was not used since some of the published data are S16 + START_POS_VALVES_ID = DenaliMessage.PAYLOAD_START_INDEX + END_POS_VALVES_ID = START_POS_VALVES_ID + 4 + + START_VALVES_STATE = END_POS_VALVES_ID + END_VALVES_STATE = START_VALVES_STATE + 4 + + START_POS_VALVES_CURR_POS_ID = END_VALVES_STATE + END_POS_VALVES_CURR_POS_ID = START_POS_VALVES_CURR_POS_ID + 4 + + START_POS_VALVES_CURR_POS = END_POS_VALVES_CURR_POS_ID + END_POS_VALVES_CURR_POS = START_POS_VALVES_CURR_POS + 2 + + START_POS_VALVES_NEXT_POS = END_POS_VALVES_CURR_POS + END_POS_VALVES_NEXT_POS = START_POS_VALVES_NEXT_POS + 2 + + + def __init__(self, can_interface, logger: Logger): + """ + DGDrainPump constructor + + @param can_interface: (DenaliCanMessenger) - Denali CAN messenger object. + @param logger: (Logger) - Dialin logger + """ + super().__init__() + self.can_interface = can_interface + self.logger = logger + + if self.can_interface is not None: + channel_id = DenaliChannels.td_sync_broadcast_ch_id + msg_id = MsgIds.MSG_ID_TD_VALVES_DATA.value + self.can_interface.register_receiving_publication_function(channel_id, msg_id, + self._handler_valves_sync) + + self.td_valves_timestamp = 0.0 + # A dictionary of the valves with the status + self.valves_status = {ValvesEnum.VBA.name: {}, + ValvesEnum.VBV.name: {}} + + def cmd_valves_broadcast_interval_override(self, ms: int, reset: int = NO_RESET) -> int: + """ + Constructs and sends broadcast time interval + Constraints: + Must be logged into TD. + Given interval must be non-zero and a multiple of the TD general task interval (50 ms). + + @param ms: Publish time interval in ms + @param reset: integer - 1 to reset a previous override, 0 to override + @returns 1 if successful, zero otherwise + """ + if not check_broadcast_interval_override_ms(ms): + return False + + reset_value = integer_to_bytearray(reset) + interval_value = integer_to_bytearray(ms) + payload = reset_value + interval_value + + message = DenaliMessage.build_message(channel_id=DenaliChannels.dialin_to_hd_ch_id, + message_id=MsgIds.MSG_ID_TD_VALVES_PUBLISH_INTERVAL_OVERRIDE_REQUEST.value, + payload=payload) + + self.logger.debug("Sending {} ms publish interval to the HD valves module".format(ms)) + # Send message + received_message = self.can_interface.send(message) + + # If there is content in message + if received_message is not None: + # Response payload is OK or not + return received_message['message'][DenaliMessage.PAYLOAD_START_INDEX] + else: + self.logger.debug("Timeout!!!!") + return False + + def cmd_set_valve_position(self, valve: int, position: int, reset: int = NO_RESET) -> int: + """ + Constructs and sends the TD valves set position for a valve + + @param valve: integer - Valve number. Defined in ValvesEnum class + @param position: integer - Position number: Defined in ValvesPositions class + @param reset: integer - 1 to reset a previous override, 0 to override + @returns 1 if successful, zero otherwise + """ + reset_value = integer_to_bytearray(reset) + vlv = integer_to_bytearray(valve) + pos = integer_to_bytearray(position) + payload = reset_value + pos + vlv + + message = DenaliMessage.build_message(channel_id=DenaliChannels.dialin_to_hd_ch_id, + message_id=MsgIds.MSG_ID_TD_PINCH_VALVE_SET_POSITION_REQUEST.value, + payload=payload) + # 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.debug("TD cmd_valve_override Timeout!!!") + return False + + def cmd_home_valve(self, valve: int) -> int: + """ + Constructs and sends the TD valves home command + + @param valve: integer - Valve number. Defined in ValvesEnum class + @returns 1 if successful, zero otherwise + """ + payload = integer_to_bytearray(valve) + + message = DenaliMessage.build_message(channel_id=DenaliChannels.dialin_to_hd_ch_id, + message_id=MsgIds.MSG_ID_TD_PINCH_VALVE_HOME_REQUEST.value, + payload=payload) + # 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.debug("TD Homing Valve Timeout!!!") + return False + + def cmd_valve_encoder_position_override(self, valve: int, position_count: int, reset: int = NO_RESET) -> int: + """ + Constructs and sends the TD valves set position for a valve + + @@param valve: integer - Valve number. Defined in ValvesEnum class + @param position_count: integer value + @param reset: integer - 1 to reset a previous override, 0 to override + @returns 1 if successful, zero otherwise + """ + reset_value = integer_to_bytearray(reset) + vlv = integer_to_bytearray(valve) + pos = integer_to_bytearray(position_count) + payload = reset_value + pos + vlv + + message = DenaliMessage.build_message(channel_id=DenaliChannels.dialin_to_hd_ch_id, + message_id=MsgIds.MSG_ID_TD_ROTARY_PINCH_VALVE_POSITION_OVERRIDE_REQUEST.value, + payload=payload) + # Send message + received_message = self.can_interface.send(message) + + # If there is content... + if received_message is not None: + + self.logger.debug("Setting {} position to {} ".format(str(ValvesEnum(valve).name), position_count)) + + # response payload is OK or not OK + return received_message['message'][DenaliMessage.PAYLOAD_START_INDEX] + else: + self.logger.debug("TD valve position override Timeout!!!") + return False + + def cmd_valve_status_override(self, valve: int, status: int, reset: int = NO_RESET) -> int: + """ + Constructs and sends the TD valves set position for a valve + + @@param valve: integer - Valve number. Defined in ValvesEnum class + @param status: integer value + @param status: integer value + @param reset: integer - 1 to reset a previous override, 0 to override + @returns 1 if successful, zero otherwise + """ + reset_value = integer_to_bytearray(reset) + vlv = integer_to_bytearray(valve) + sts = integer_to_bytearray(status) + payload = reset_value + sts + vlv + + message = DenaliMessage.build_message(channel_id=DenaliChannels.dialin_to_hd_ch_id, + message_id=MsgIds.MSG_ID_TD_ROTARY_PINCH_VALVE_STATUS_OVERRIDE_REQUEST.value, + payload=payload) + # Send message + received_message = self.can_interface.send(message) + + # If there is content... + if received_message is not None: + + self.logger.debug("Setting {} status to {} ".format(str(ValvesEnum(valve).name), status)) + + # response payload is OK or not OK + return received_message['message'][DenaliMessage.PAYLOAD_START_INDEX] + else: + self.logger.debug("TD valve status override Timeout!!!") + return False + + def cmd_valve_modify_encoder_position_by_offset(self, valve: int, counts: int) -> int: + """ + Constructs and sends a given valve to change position by a + given number of encoder counts. + + @@param valve: integer - Valve number. Defined in ValvesEnum class + @param count: integer value + @param reset: integer - 1 to reset a previous override, 0 to override + @returns 1 if successful, zero otherwise + """ + vlv = integer_to_bytearray(valve) + pos = integer_to_bytearray(counts) + payload = pos + vlv + + message = DenaliMessage.build_message(channel_id=DenaliChannels.dialin_to_hd_ch_id, + message_id=MsgIds.MSG_ID_TD_ROTARY_PINCH_VALVE_POSITION_OVERRIDE_REQUEST.value, + payload=payload) + # Send message + received_message = self.can_interface.send(message) + + # If there is content... + if received_message is not None: + + self.logger.debug("Setting {} position by {} ".format(str(ValvesEnum(valve).name), counts)) + + # response payload is OK or not OK + return received_message['message'][DenaliMessage.PAYLOAD_START_INDEX] + else: + self.logger.debug("TD valve position override Timeout!!!") + return False + + @publish(["td_valves_timestamp","valves_status"]) + def _handler_valves_sync(self, message: dict, timestamp=0.0) -> None: + """ + Handles published valves data messages. TD valves data are captured + for reference. + + @param message: published TD valves data message + @returns none + """ + vlv_id = struct.unpack('i', bytearray( + message['message'][self.START_POS_VALVES_ID:self.END_POS_VALVES_ID]))[0] + state_id = struct.unpack('i', bytearray( + message['message'][self.START_VALVES_STATE:self.END_VALVES_STATE]))[0] + pos_id = struct.unpack('i', bytearray( + message['message'][self.START_POS_VALVES_CURR_POS_ID:self.END_POS_VALVES_CURR_POS_ID]))[0] + pos_cnt = struct.unpack('h', bytearray( + message['message'][self.START_POS_VALVES_CURR_POS:self.END_POS_VALVES_CURR_POS]))[0] + next_pos = struct.unpack('h', bytearray( + message['message'][self.START_POS_VALVES_NEXT_POS:self.END_POS_VALVES_NEXT_POS]))[0] + + # To make sure values of the enums are not out of range + if ValvesEnum.has_value(vlv_id) and ValvesPositions.has_value(pos_id) and ValvesStates.has_value(pos_id): + vlv_name = ValvesEnum(vlv_id).name + # Update the valves dictionary + self.valves_status[vlv_name] = {'Valve': vlv_name, 'PosID': ValvesPositions(pos_id).name, 'PosCnt': pos_cnt, + 'Cmd': next_pos, 'State': ValvesStates(state_id).name} + self.td_valves_timestamp = timestamp \ No newline at end of file Index: leahi_dialin/td/modules/voltages.py =================================================================== diff -u --- leahi_dialin/td/modules/voltages.py (revision 0) +++ leahi_dialin/td/modules/voltages.py (revision 2138d06d100fdcf23f2e9069f35ee2fdee62008f) @@ -0,0 +1,194 @@ +########################################################################### +# +# Copyright (c) 2021-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 voltages.py +# +# @author (last) Micahel Garthwaite +# @date (last) 03-Mar-2023 +# @author (original) Sean Nash +# @date (original) 15-Apr-2021 +# +############################################################################ +import struct +from enum import unique +from logging import Logger + +from leahi_dialin.td.constants import RESET, NO_RESET +from leahi_dialin.protocols.CAN import DenaliMessage, DenaliChannels +from leahi_dialin.utils.base import AbstractSubSystem, publish, DialinEnum +from leahi_dialin.utils.conversions import integer_to_bytearray, float_to_bytearray +from leahi_dialin.utils.checks import check_broadcast_interval_override_ms +from leahi_dialin.common.msg_defs import MsgIds, MsgFieldPositions + + +# Monitored voltages +@unique +class TDMonitoredVoltages(DialinEnum): + MONITORED_LINE_1_2V = 0 # Processor voltage (1.2V) + MONITORED_LINE_3_3V = 1 # Logic voltage (3.3V) + MONITORED_LINE_5V_LOGIC = 2 # Logic voltage (5V) + MONITORED_LINE_5V_SENSORS = 3 # Sensors voltage (5V) + MONITORED_LINE_24V = 4 # Actuators voltage (24V) + MONITORED_LINE_24V_REGEN = 5 # Actuators regen voltage (24V) + MONITORED_LINE_FPGA_REF_V = 6 # FPGA ADC reference voltage (1V) + MONITORED_LINE_PBA_REF_V = 7 # PBA ADC reference voltage (3V) + MONITORED_LINE_FPGA_VCC_V = 8 # FPGA Vcc (3V) + MONITORED_LINE_FPGA_AUX_V = 9 # FPGA Vaux (3V) + MONITORED_LINE_FPGA_PVN_V = 10 # FPGA Vpvn (1V) + NUM_OF_MONITORED_VOLTAGE_LINES = 11 # Number of TD operation modes + +class TDVoltages(AbstractSubSystem): + """ + Treatment Delivery (TD) Dialin API sub-class for voltage monitor related commands and data. + """ + + def __init__(self, can_interface, logger: Logger): + """ + TDVoltages constructor + + """ + super().__init__() + self.can_interface = can_interface + self.logger = logger + + if self.can_interface is not None: + channel_id = DenaliChannels.td_sync_broadcast_ch_id + msg_id = MsgIds.MSG_ID_TD_VOLTAGES_DATA.value + self.can_interface.register_receiving_publication_function(channel_id, msg_id, + self._handler_monitored_voltages_sync) + self.monitored_voltages = [0.0] * TDMonitoredVoltages.NUM_OF_MONITORED_VOLTAGE_LINES.value + self.TD_voltages_timestamp = 0.0 + + + @publish(["td_voltages_timestamp","monitored_voltages"]) + def _handler_monitored_voltages_sync(self, message, timestamp=0.0): + """ + Handles published TD monitored voltages data messages. Voltage data are captured + for reference. + + @param message: published monitored voltages data message + @return: none + """ + + v12 = struct.unpack('f', bytearray( + message['message'][MsgFieldPositions.START_POS_FIELD_1:MsgFieldPositions.END_POS_FIELD_1])) + v33 = struct.unpack('f', bytearray( + message['message'][MsgFieldPositions.START_POS_FIELD_2:MsgFieldPositions.END_POS_FIELD_2])) + v5l = struct.unpack('f', bytearray( + message['message'][MsgFieldPositions.START_POS_FIELD_3:MsgFieldPositions.END_POS_FIELD_3])) + v5s = struct.unpack('f', bytearray( + message['message'][MsgFieldPositions.START_POS_FIELD_4:MsgFieldPositions.END_POS_FIELD_4])) + v24 = struct.unpack('f', bytearray( + message['message'][MsgFieldPositions.START_POS_FIELD_5:MsgFieldPositions.END_POS_FIELD_5])) + v24g = struct.unpack('f', bytearray( + message['message'][MsgFieldPositions.START_POS_FIELD_6:MsgFieldPositions.END_POS_FIELD_6])) + vfr = struct.unpack('f', bytearray( + message['message'][MsgFieldPositions.START_POS_FIELD_7:MsgFieldPositions.END_POS_FIELD_7])) + vpr = struct.unpack('f', bytearray( + message['message'][MsgFieldPositions.START_POS_FIELD_8:MsgFieldPositions.END_POS_FIELD_8])) + vfc = struct.unpack('f', bytearray( + message['message'][MsgFieldPositions.START_POS_FIELD_9:MsgFieldPositions.END_POS_FIELD_9])) + vfa = struct.unpack('f', bytearray( + message['message'][MsgFieldPositions.START_POS_FIELD_10:MsgFieldPositions.END_POS_FIELD_10])) + vfp = struct.unpack('f', bytearray( + message['message'][MsgFieldPositions.START_POS_FIELD_11:MsgFieldPositions.END_POS_FIELD_11])) + + self.monitored_voltages[TDMonitoredVoltages.MONITORED_LINE_1_2V.value] = v12[0] + self.monitored_voltages[TDMonitoredVoltages.MONITORED_LINE_3_3V.value] = v33[0] + self.monitored_voltages[TDMonitoredVoltages.MONITORED_LINE_5V_LOGIC.value] = v5l[0] + self.monitored_voltages[TDMonitoredVoltages.MONITORED_LINE_5V_SENSORS.value] = v5s[0] + self.monitored_voltages[TDMonitoredVoltages.MONITORED_LINE_24V.value] = v24[0] + self.monitored_voltages[TDMonitoredVoltages.MONITORED_LINE_24V_REGEN.value] = v24g[0] + self.monitored_voltages[TDMonitoredVoltages.MONITORED_LINE_FPGA_REF_V.value] = vfr[0] + self.monitored_voltages[TDMonitoredVoltages.MONITORED_LINE_PBA_REF_V.value] = vpr[0] + self.monitored_voltages[TDMonitoredVoltages.MONITORED_LINE_FPGA_VCC_V.value] = vfc[0] + self.monitored_voltages[TDMonitoredVoltages.MONITORED_LINE_FPGA_AUX_V.value] = vfa[0] + self.monitored_voltages[TDMonitoredVoltages.MONITORED_LINE_FPGA_PVN_V.value] = vfp[0] + self.td_voltages_timestamp = timestamp + + def cmd_monitored_voltage_override(self, signal: int = 0, volts: float = 0.0, reset: int = NO_RESET) -> int: + """ + Constructs and sends the TD monitored voltage override command + Constraints: + Must be logged into TD. + Given signal must be valid member of TDMonitoredVoltages enum + + @param signal: integer - ID of signal to override + @param volts: float - value (in volts) to override signal with + @param reset: integer - 1 to reset a previous override, 0 to override + @return: 1 if successful, zero otherwise + """ + + rst = integer_to_bytearray(reset) + vlt = float_to_bytearray(volts) + idx = integer_to_bytearray(signal) + payload = rst + vlt + idx + + message = DenaliMessage.build_message(channel_id=DenaliChannels.dialin_to_td_ch_id, + message_id=MsgIds.MSG_ID_TD_VOLTAGE_OVERRIDE_REQUEST.value, + payload=payload) + + self.logger.debug("override monitored TD voltage for signal " + str(signal)) + + # Send message + received_message = self.can_interface.send(message) + + # If there is content... + if received_message is not None: + if reset == RESET: + str_res = "reset back to normal. " + else: + str_res = str(volts) + " V. " + self.logger.debug("Monitored TD voltage overridden to " + str_res + + str(received_message['message'][DenaliMessage.PAYLOAD_START_INDEX])) + # response payload is OK or not OK + return received_message['message'][DenaliMessage.PAYLOAD_START_INDEX] + else: + self.logger.debug("Timeout!!!!") + return False + + def cmd_monitored_voltages_broadcast_interval_override(self, ms: int = 1000, reset: int = NO_RESET) -> int: + """ + Constructs and sends the monitored TD voltages broadcast interval override command + Constraints: + Must be logged into TD. + Given interval must be non-zero and a multiple of the TD general task interval (50 ms). + + @param ms: integer - interval (in ms) to override with + @param reset: integer - 1 to reset a previous override, 0 to override + @return: 1 if successful, zero otherwise + """ + + if not check_broadcast_interval_override_ms(ms): + return False + + rst = integer_to_bytearray(reset) + mis = integer_to_bytearray(ms) + payload = rst + mis + + message = DenaliMessage.build_message(channel_id=DenaliChannels.dialin_to_td_ch_id, + message_id=MsgIds.MSG_ID_TD_VOLTAGE_PUBLISH_INTERVAL_OVERRIDE_REQUEST.value, + payload=payload) + + self.logger.debug("override monitored TD voltages broadcast interval") + + # Send message + received_message = self.can_interface.send(message) + + # If there is content... + if received_message is not None: + if reset == RESET: + str_res = "reset back to normal: " + else: + str_res = str(ms) + " ms: " + self.logger.debug("TD monitored voltages broadcast interval overridden to " + str_res + + str(received_message['message'][DenaliMessage.PAYLOAD_START_INDEX])) + # response payload is OK or not OK + return received_message['message'][DenaliMessage.PAYLOAD_START_INDEX] + else: + self.logger.debug("Timeout!!!!") + return False Index: leahi_dialin/td/proxies/__init__.py =================================================================== diff -u --- leahi_dialin/td/proxies/__init__.py (revision 0) +++ leahi_dialin/td/proxies/__init__.py (revision 2138d06d100fdcf23f2e9069f35ee2fdee62008f) @@ -0,0 +1 @@ \ No newline at end of file Index: leahi_dialin/td/proxies/dd_proxy.py =================================================================== diff -u --- leahi_dialin/td/proxies/dd_proxy.py (revision 0) +++ leahi_dialin/td/proxies/dd_proxy.py (revision 2138d06d100fdcf23f2e9069f35ee2fdee62008f) @@ -0,0 +1 @@ \ No newline at end of file Index: leahi_dialin/td/proxies/ro_proxy.py =================================================================== diff -u --- leahi_dialin/td/proxies/ro_proxy.py (revision 0) +++ leahi_dialin/td/proxies/ro_proxy.py (revision 2138d06d100fdcf23f2e9069f35ee2fdee62008f) @@ -0,0 +1 @@ \ No newline at end of file Index: leahi_dialin/td/treatment_delivery.py =================================================================== diff -u --- leahi_dialin/td/treatment_delivery.py (revision 0) +++ leahi_dialin/td/treatment_delivery.py (revision 2138d06d100fdcf23f2e9069f35ee2fdee62008f) @@ -0,0 +1,317 @@ +########################################################################### +# +# 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 treatment_delivery.py +# +# @author (last) Dara Navaei +# @date (last) 26-Feb-2024 +# @author (original) Peter Lucia +# @date (original) 02-Apr-2020 +# +############################################################################ +import struct + +from .modules.air_pump import TDAirPump +from .modules.air_trap import TDAirTrap +from .modules.alarms import TDAlarms +from .modules.blood_flow import TDBloodFlow +from .modules.bubble_detector import TDBubbleDectector +from .modules.buttons import TDButtons +from .modules.pressure_sensors import TDPressureSensors +from .modules.switches import TDSwitches +from .modules.valves import TDValves +from .modules.voltages import TDVoltages +from ..common.msg_defs import MsgIds, MsgFieldPositions +from ..common.td_defs import TDOpModes +from ..protocols.CAN import DenaliMessage, DenaliCanMessenger, DenaliChannels +from ..utils.base import AbstractSubSystem, publish, LogManager +from ..utils.checks import check_broadcast_interval_override_ms +from ..utils.conversions import integer_to_bytearray, unsigned_short_to_bytearray, bytearray_to_integer, \ + bytearray_to_byte + + +class TD(AbstractSubSystem): + """ + Treatment Delivery (TD) Dialin object API. + It provides the basic interface to communicate with the TD firmware. + """ + # TD debug event max count + _TD_DEBUG_EVENT_LIST_COUNT = 10 + _TD_DEBUG_EVENT_MSG_LEN_INDEX = 5 + + # TD login password + TD_LOGIN_PASSWORD = '123' + + # UI version message field positions + START_POS_MAJOR = DenaliMessage.PAYLOAD_START_INDEX + END_POS_MAJOR = START_POS_MAJOR + 1 + START_POS_MINOR = END_POS_MAJOR + END_POS_MINOR = START_POS_MINOR + 1 + START_POS_MICRO = END_POS_MINOR + END_POS_MICRO = START_POS_MICRO + 1 + START_POS_BUILD = END_POS_MICRO + END_POS_BUILD = START_POS_BUILD + 2 + START_POS_COMPATIBILITY_REV = END_POS_BUILD + END_POS_COMPATIBILITY_REV = START_POS_COMPATIBILITY_REV + 4 + + def __init__(self, can_interface="can0", log_level=None): + """ + TD object provides test/service commands for the TD sub-system. + + >> TD_object = TD('can0') + >> TD_object = TD(can_interface='can0', log_level="DEBUG") + + Possible log levels: + ["DEBUG", "INFO", "WARNING", "ERROR", "CRITICAL", "CAN_ONLY", "PRINT_ONLY"] + + @param can_interface: (str) CANBus interface name, e.g. "can0" + @param log_level: (str) Logging level, defaults to None + """ + + super().__init__() + self._log_manager = LogManager(log_level=log_level, log_filepath=self.__class__.__name__ + ".log") + self.logger = self._log_manager.logger + + # Create listener + self.can_interface = DenaliCanMessenger(can_interface=can_interface, + logger=self.logger) + self.can_interface.start() + self.callback_id = None + # register handler for TD operation mode broadcast messages + if self.can_interface is not None: + channel_id = DenaliChannels.td_sync_broadcast_ch_id + msg_id = MsgIds.MSG_ID_TD_OP_MODE_DATA.value + self.can_interface.register_receiving_publication_function(channel_id, msg_id, + self._handler_td_op_mode_sync) + + self.can_interface.register_receiving_publication_function(channel_id, + MsgIds.MSG_ID_TD_DEBUG_EVENT.value, + self._handler_td_debug_event_sync) + + self.can_interface.register_receiving_publication_function(DenaliChannels.ui_to_td_ch_id, + MsgIds.MSG_ID_UI_VERSION_INFO_RESPONSE.value, + self._handler_ui_version_response_sync) + + # create properties + self.td_op_mode_timestamp = 0.0 + self.td_debug_events_timestamp = 0.0 + self.ui_version_info_response_timestamp = 0.0 + self.td_operation_mode = TDOpModes.MODE_INIT.value + self.td_operation_sub_mode = 0 + self.td_logged_in = False + self.td_set_logged_in_status(False) + self.ui_version = None + self.td_debug_events = [''] * self._TD_DEBUG_EVENT_LIST_COUNT + self.td_debug_event_index = 0 + self.td_last_debug_event = '' + + # Create command groups + + self.air_pump = TDAirPump(self.can_interface, self.logger) + self.air_trap = TDAirTrap(self.can_interface, self.logger) + self.alarms = TDAlarms(self.can_interface, self.logger) + self.blood_flow = TDBloodFlow(self.can_interface, self.logger) + self.bubbles = TDBubbleDectector(self.can_interface, self.logger) + self.buttons = TDButtons(self.can_interface, self.logger) + self.pressure_sensors = TDPressureSensors(self.can_interface, self.logger) + self.switches = TDSwitches(self.can_interface, self.logger) + self.valves = TDValves(self.can_interface, self.logger) + self.voltages = TDVoltages(self.can_interface, self.logger) + + @publish(["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 + + for i in range(0, message_length): + # Loop through the length and get the + char, char_index = bytearray_to_byte(payload, index + i, False) + temp_message += chr(char) + + self.td_debug_events_timestamp = timestamp + self.td_debug_events.insert(self.td_debug_event_index, temp_message) + self.td_last_debug_event = temp_message + + self.td_debug_event_index += 1 + if self.td_debug_event_index == self._TD_DEBUG_EVENT_LIST_COUNT: + self.td_debug_event_index = 0 + + @publish(["td_logged_in"]) + def td_set_logged_in_status(self, logged_in: bool = False): + """ + Callback for td logged in status change. + @param logged_in boolean logged in status for TD + @return: none + """ + self.td_logged_in = logged_in + + @publish(["td_op_mode_timestamp","td_operation_mode", "td_operation_sub_mode"]) + def _handler_td_op_mode_sync(self, message, timestamp = 0.0): + """ + Handles published TD operation mode messages. Current TD operation mode + is captured for reference. + + @param message: published TD operation mode broadcast message + @return: None + """ + mode = struct.unpack('i', bytearray( + message['message'][MsgFieldPositions.START_POS_FIELD_1:MsgFieldPositions.END_POS_FIELD_1])) + smode = struct.unpack('i', bytearray( + message['message'][MsgFieldPositions.START_POS_FIELD_2:MsgFieldPositions.END_POS_FIELD_2])) + + self.td_operation_mode = mode[0] + self.td_operation_sub_mode = smode[0] + self.td_op_mode_timestamp = timestamp + + + def _handler_ui_version_response_sync(self,message, timestamp = 0.0): + """ + Handler for response from TD regarding its version. + + @param message: response message from TD regarding valid treatment parameter ranges.\n + U08 Major \n + U08 Minor \n + U08 Micro \n + U16 Build \n + U32 Compatibility revision + + @return: None if not successful, the version string if unpacked successfully + """ + major = struct.unpack(' 0 for each in [major, minor, micro, build, compatibility]]): + self.ui_version = f"v{major[0]}.{minor[0]}.{micro[0]}-{build[0]}.{compatibility[0]}" + self.logger.debug(f"UI VERSION: {self.ui_version}") + + else: + self.ui_version = None + self.logger.debug("Failed to retrieve UI Version.") + + def cmd_log_in_to_td(self, resend: bool = False) -> int: + """ + Constructs and sends a login command via CAN bus. Login required before \n + other commands can be sent to the TD. + + @param resend: (bool) if False (default), try to login once. Otherwise, tries to login indefinitely + @return: 1 if logged in, 0 if log in failed + """ + + message = DenaliMessage.build_message(channel_id=DenaliChannels.dialin_to_td_ch_id, + message_id=MsgIds.MSG_ID_TESTER_LOGIN_REQUEST.value, + payload=list(map(int, map(ord, self.TD_LOGIN_PASSWORD)))) + + self.logger.debug("Logging in...") + + # Send message + received_message = self.can_interface.send(message, resend=resend) + + if received_message is not None: + if received_message['message'][DenaliMessage.PAYLOAD_START_INDEX] == 1: + self.logger.debug("Success: Logged In") + self.td_set_logged_in_status(True) + self._send_td_checkin_message() # Timer starts interval first + self.can_interface.transmit_interval_dictionary[self.callback_id].start() + else: + self.logger.debug("Failure: Log In Failed.") + return received_message['message'][DenaliMessage.PAYLOAD_START_INDEX] + else: + self.logger.debug("Login Timeout!!!!") + return False + + def cmd_td_set_operation_mode(self, new_mode: int = 0) -> int: + """ + Constructs and sends a set operation mode request command via CAN bus. + Constraints: + Must be logged into TD. + Transition from current to requested op mode must be legal. + NOTE: for POST the TD device shall be in Standby Mode + + @param new_mode: ID of operation mode to transition to + TD_OP_MODE_FAULT = 0 + TD_OP_MODE_SERVICE = 1 + TD_OP_MODE_INIT_POST = 2 + TD_OP_MODE_STANDBY = 3 + TD_OP_MODE_TREATMENT_PARAMS = 4 + TD_OP_MODE_PRE_TREATMENT = 5 + TD_OP_MODE_TREATMENT = 6 + TD_OP_MODE_POST_TREATMENT = 7 + + @return: 1 if successful, zero otherwise + + """ + + payload = integer_to_bytearray(new_mode) + + message = DenaliMessage.build_message(channel_id=DenaliChannels.dialin_to_td_ch_id, + message_id=MsgIds.MSG_ID_TD_SET_OP_MODE_REQUEST.value, + payload=payload) + + self.logger.debug("Requesting TD mode change to " + str(new_mode)) + + # Send message + received_message = self.can_interface.send(message) + + if received_message is not None: + if received_message['message'][DenaliMessage.PAYLOAD_START_INDEX] == 1: + self.logger.debug("Success: Mode change accepted") + else: + self.logger.debug("Failure: Mode change rejected.") + return received_message['message'][DenaliMessage.PAYLOAD_START_INDEX] + else: + self.logger.debug("TD mode change request Timeout!!!!") + return False + + + def cmd_td_software_reset_request(self) -> None: + """ + Constructs and sends an TD software reset request via CAN bus. + Constraints: + Must be logged into TD. + + @return: None + """ + + message = DenaliMessage.build_message(channel_id=DenaliChannels.dialin_to_td_ch_id, + message_id=MsgIds.MSG_ID_TD_SOFTWARE_RESET_REQUEST.value) + + self.logger.debug("requesting TD software reset") + + # Send message + self.can_interface.send(message, 0) + self.logger.debug("Sent request to TD to reset...") + self.td_set_logged_in_status(False) + + def cmd_request_ui_version(self) -> None: + """ + Constructs and sends a ui version request to the TD. + + @return: none + """ + + message = DenaliMessage.build_message(channel_id=DenaliChannels.TD_to_ui_ch_id, + message_id=MsgIds.MSG_ID_TD_UI_VERSION_INFO_REQUEST.value) + + self.logger.debug("Sending an UI version request to the TD.") + self.can_interface.send(message, 0) + + + Index: leahi_dialin/ui/__init__.py =================================================================== diff -u --- leahi_dialin/ui/__init__.py (revision 0) +++ leahi_dialin/ui/__init__.py (revision 2138d06d100fdcf23f2e9069f35ee2fdee62008f) @@ -0,0 +1 @@ \ No newline at end of file Index: leahi_dialin/utils/__init__.py =================================================================== diff -u --- leahi_dialin/utils/__init__.py (revision 0) +++ leahi_dialin/utils/__init__.py (revision 2138d06d100fdcf23f2e9069f35ee2fdee62008f) @@ -0,0 +1,10 @@ +from .base import * +from .checks import * +from .helpers import * +from .conversions import * +from .excel_ops import * +from .nv_ops_utils import * +from .singleton import * +from .data_logger import DataLogger +YES = 1 +NO = 0 Index: leahi_dialin/utils/base.py =================================================================== diff -u --- leahi_dialin/utils/base.py (revision 0) +++ leahi_dialin/utils/base.py (revision 2138d06d100fdcf23f2e9069f35ee2fdee62008f) @@ -0,0 +1,323 @@ +########################################################################### +# +# 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 base.py +# +# @author (last) Micahel Garthwaite +# @date (last) 16-May-2023 +# @author (original) Peter Lucia +# @date (original) 22-Jun-2020 +# +############################################################################ +import logging +import os +from abc import ABC, abstractmethod +from datetime import datetime +from enum import Enum +from threading import Timer + +INTERVAL_1s = 1 +INTERVAL_5s = 5 +INTERVAL_10s = 10 +INTERVAL_60s = 60 + +class AbstractObserver(ABC): + """ + Publicly accessible parent class for all observers. + + The update method will receive data when data is made available + """ + + @abstractmethod + def update(self): + """ + Attach an observer + """ + pass + + +class _FauxLogger: + + def __init__(self, printing_enabled=False): + self.printing_enabled = printing_enabled + + def debug(self, msg): + if self.printing_enabled: + print("DEBUG: {0}".format(msg)) + + def info(self, msg): + if self.printing_enabled: + print("INFO: {0}".format(msg)) + + def warn(self, msg): + if self.printing_enabled: + print("WARN: {0}".format(msg)) + + def warning(self, msg): + if self.printing_enabled: + print("WARNING: {0}".format(msg)) + + def error(self, msg): + if self.printing_enabled: + print("ERROR: {0}".format(msg)) + + def critical(self, msg): + if self.printing_enabled: + print("CRITICAL: {0}".format(msg)) + + +class LogManager: + LOG_FMT = '%(asctime)s,%(msecs)d %(levelname)-8s [%(filename)s:%(lineno)d] %(message)s' + LOG_FMT_NO_METADATA = '%(message)s' + LOG_DT_FMT = '%m-%d-%Y:%H:%M:%S' + + def __init__(self, log_level=None, log_filepath="Dialin.log"): + """ + + @param log_level: (str) or (None) if not set, contains the logging level + @param log_filepath: (str) the log filepath + """ + + self.log_level = log_level + self.logging_enabled = self.log_level_enables_logging(log_level) + self.log_filepath = self.get_available_log_path(log_filepath) + self.logger = None + self.configure_logging(self.log_filepath) + + def log_level_enables_logging(self, log_level: str): + """ + Check if the log level string is a valid logging level + + @param log_level: (str) the logging level + @return: True if the log level is valid, False otherwise + """ + return log_level is not None and log_level.upper() in self.get_logging_levels() + + @staticmethod + def get_logging_levels(): + """ + Gets all possible logging levels + + @return: All possible logging levels + """ + return ["DEBUG", "INFO", "WARNING", "ERROR", "CRITICAL", "CAN_ONLY", "PRINT_ONLY"] + + def request_new_log_path(self, path: str): + """ + Clears the logger, gets a new log filepath + and configures the logger to use it. + + @param path: the requested log filepath + @return: The new log filepath + """ + self.clear_logger() + self.log_filepath = self.get_available_log_path(path) + self.configure_logging(self.log_filepath) + return self.log_filepath + + def clear_logger(self): + """ + If the logger has been created, clear its handlers + + @return: True if successful, False otherwise + """ + + # remove existing handlers + if self.logger is not None: + for handler in self.logger.handlers: + self.logger.removeHandler(handler) + return True + return False + + def configure_logging(self, log_path): + """ + Sets up the logger to use the provided log path + + @param log_path: Path to the log file + @return: True if success, False otherwise + """ + if self.logger is not None: + print("Logger already configured. Please clear the logger first.") + return False + + # configure the logging + if self.logging_enabled and self.log_level not in ["PRINT_ONLY", "CAN_ONLY"]: + numeric_level = getattr(logging, self.log_level.upper(), logging.ERROR) + self.logger = logging.getLogger("Dialin") + self.logger.setLevel(numeric_level) + + fh = logging.FileHandler(log_path) + fh.setLevel(numeric_level) + ch = logging.StreamHandler() + ch.setLevel(numeric_level) + formatter = logging.Formatter(fmt=self.LOG_FMT, + datefmt=self.LOG_DT_FMT) + fh.setFormatter(formatter) + ch.setFormatter(formatter) + + self.logger.addHandler(fh) + self.logger.addHandler(ch) + else: + self.logger = _FauxLogger(printing_enabled=self.log_level == "PRINT_ONLY") + + return True + + def print_and_log(self, message, log_level=logging.DEBUG): + """ + Prints a message if its severity is >= the current log level. + Also logs the message. + + @param message: The message to print and log + @param log_level: The logging level, indicates the severity of the message + + @return:: None + """ + if not self.logging_enabled: + return + + if log_level == logging.DEBUG: + self.logger.debug(message) + elif log_level == logging.INFO: + self.logger.info(message) + elif log_level == logging.WARNING: + self.logger.warning(message) + elif log_level == logging.ERROR: + self.logger.error(message) + elif log_level == logging.CRITICAL: + self.logger.critical(message) + + @staticmethod + def get_available_log_path(filepath: str): + """ + Gets an available log path from filepath + appends integer to the end if file already exists. + + @param filepath: The full path to the file + @return: (str) The available log filepath + """ + + if not os.path.exists(filepath): + return filepath + + path, ext = os.path.splitext(filepath) + i = 0 + while os.path.exists("{0}{1}{2}".format(path, i, ext)): + i += 1 + return "{0}{1}{2}".format(path, i, ext) + + +class AbstractSubSystem: + + @abstractmethod + def __init__(self): + """ + Initialization function for the sub system + # The abstract base class requires all abstract methods are overridden by children classes + + """ + self._observers = [] + self._datetime_fmt = "%m.%d.%Y_%I.%M.%S.%f" + pass + + def attach(self, observer: AbstractObserver): + """ + Attach an observer so it is updated upon published events + """ + self._observers.append(observer) + + def detach(self, observer: AbstractObserver): + """ + Detach an observer + """ + self._observers.remove(observer) + + +def publish(keys): + """ + Decorator that accepts a list of variable names to publish + To be used in any AbstractSubSystem + + @param keys: The variable names to publish + @return: A function that will take a function and return another function + """ + + def _decorator(func): + """ + + @param func: The handler function + @return: The function to wrap around _publish + """ + + def _wrapper(self, *args, **kwargs): + func(self, *args, **kwargs) + result = {} + + if not self._observers: + return None + + result["datetime"] = datetime.now() + result["subsystem"] = self.__class__.__name__ + + for key in keys: + result[key] = getattr(self, key) + + for observer in self._observers: + observer.update(result) + + return _wrapper + + return _decorator + + +class DialinEnum(Enum): + + @classmethod + def has_value(cls, value): + return value in cls._value2member_map_ + + +class AlarmEnum(Enum): + def __init__(self, *args): + cls = self.__class__ + if any(self.value == member.value for member in cls): + raise ValueError("aliases not allowed: %r --> %r" % (self.name, cls(self.value).name)) + + @classmethod + def has_value(cls, value): + return value in cls._value2member_map_ + + +class IntervalTimer(object): + """ + A class object that is used to execute a function on a timed interval. + Timed interval auto starts on object creation. + Uses to send CAN messages at a specified interval. + + """ + def __init__(self, interval, function, *args, **kwargs): + self._timer = None + self.interval = interval + self.function = function + self.args = args + self.kwargs = kwargs + self.is_running = False + + def _run(self): + self.is_running = False + self.start() + self.function(*self.args, **self.kwargs) + + def start(self): + if not self.is_running: + self._timer = Timer(self.interval, self._run) + self._timer.daemon = True + self._timer.start() + self.is_running = True + + def stop(self): + self._timer.cancel() + self.is_running = False Index: leahi_dialin/utils/checks.py =================================================================== diff -u --- leahi_dialin/utils/checks.py (revision 0) +++ leahi_dialin/utils/checks.py (revision 2138d06d100fdcf23f2e9069f35ee2fdee62008f) @@ -0,0 +1,27 @@ +########################################################################### +# +# Copyright (c) 2021-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 checks.py +# +# @author (last) Quang Nguyen +# @date (last) 07-Jul-2021 +# @author (original) Sean Nash +# @date (original) 27-May-2021 +# +############################################################################ + +def check_broadcast_interval_override_ms(ms: int): + """ + Checks whether a given broadcast interval override (in ms) is valid. + + @param ms: (int) number of ms being used to override a broadcast interval + @return: True if valid, False if not + """ + result = False + if ms > 0 and ms % 50 == 0: + result = True + return result Index: leahi_dialin/utils/conversions.py =================================================================== diff -u --- leahi_dialin/utils/conversions.py (revision 0) +++ leahi_dialin/utils/conversions.py (revision 2138d06d100fdcf23f2e9069f35ee2fdee62008f) @@ -0,0 +1,208 @@ +########################################################################### +# +# 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 conversions.py +# +# @author (last) Dara Navaei +# @date (last) 20-Jun-2022 +# @author (original) Peter Lucia +# @date (original) 02-Apr-2020 +# +############################################################################ +import struct +from typing import List, Union + +# https://docs.python.org/3/library/struct.html#format-characters + + +def byte_to_bytearray(val: int) -> bytes: + """ + Converts a byte value into a byte array (little endian) + + @param val: (int) integer to convert to byte array + @return: byte array + """ + if type(val) != int: + raise ValueError("Expected integer but received {0} with type {1}".format(val, type(val))) + return struct.pack(" bytes: + """ + Converts a byte value into a byte array (little endian) + + @param val: (int) integer to convert to byte array + @return: byte array + """ + if type(val) != int: + raise ValueError("Expected integer but received {0} with type {1}".format(val, type(val))) + return struct.pack(" bytes: + """ + Converts a short integer (2 bytes) value into a byte array (little endian) + + @param val: (int) integer to convert to byte array + @return: byte array + """ + if type(val) != int: + raise ValueError("Expected integer but received {0} with type {1}".format(val, type(val))) + return struct.pack(" bytes: + """ + Converts an unsigned short integer (2 bytes) value into a byte array (little endian) + + @param val: (int) integer to convert to byte array + @return: byte array + """ + if type(val) != int or val < 0: + raise ValueError("Expected unsigned integer but received {0} with type {1}".format(val, type(val))) + return struct.pack(" bytes: + """ + Converts an integer value into a byte array (little endian) + + @param val: integer to convert to byte array, bool is accepted + @return: byte array + """ + if type(val) != bool and type(val) != int: + raise ValueError("Expected integer but received {0} with type {1}".format(val, type(val))) + return struct.pack(" bytes: + """ + Converts an integer value into a byte array (little endian) + + @param val: (int) integer to convert to byte array + @return: byte array + """ + if type(val) != int or val < 0: + raise ValueError("Expected unsigned integer but received {0} with type {1}".format(val, type(val))) + return struct.pack(" bytes: + """ + Converts a float value into a byte array (little endian) + + @param val: float to convert to byte array, int is accepted + @return:: byte array + """ + if type(val) != float and type(val) != int: + raise ValueError("Expected float but received {0} with type {1}".format(val, type(val))) + return struct.pack(' List[int]: + """ + Converts an integer to a bit array. For direct conversion to binary please use bin(val), + as this function has a separate purpose + @param val: (int) the number to convert + @param num_bits: (int) the number of bits needed + @return: (List[int]) The number represented in binary form as a list of 1's and 0's + """ + if type(val) != int: + raise ValueError("Expected integer but received {0} with type {1}".format(val, type(val))) + return [(val >> bit) & 1 for bit in range(num_bits - 1, -1, -1)] + + +def bytearray_to_value(fmt: str, buf: bytes) -> int: + """ + Converts the buffer to the type of value specified in the frm as format + + @param fmt: string format to convert the buffer to. + @param buf: the buffer of bytes to be converted to the value + + @return: the converted value of buffer to the type of fmt if successful or None if not. + """ + value = None + try: + value = struct.unpack(fmt, bytearray(buf)) + except BaseException as err: + print("err: ", err) + + if value is not None and len(value) > 0: + value = value[0] + + return value + + +def bytearray_to_byte(buffer: bytes, index: int, signed: bool = True) -> [int, int]: + """ + Converts the buffer of bytes to a value of one(1) byte integer + + @param buffer: (bytes) the source buffer + @param index: (int) the start index of reading the source buffer + @param signed: (bool) convert to signed or unsigned value + @return: pair of the [value and incremented index by the length. + """ + length = 1 # for a byte + value = bytearray_to_value("b" if signed else "B", buffer[index:index + length]) + return value, index + length + + +def bytearray_to_short(buffer: bytes, index: int, signed: bool = True) -> [int, int]: + """ + Converts the buffer of bytes to a value of two(2) byte integer + @param buffer: (bytes) the source buffer + @param index: (int) the start index of reading the source buffer + @param signed: (bool) convert to signed or unsigned value + @return: pair of the [value and incremented index by the length. + """ + length = 2 # for a short + value = bytearray_to_value("h" if signed else "H", buffer[index:index + length]) + return value, index + length + + +def bytearray_to_integer(buffer: bytes, index: int, signed: bool = True) -> [int, int]: + """ + Converts the buffer of bytes to a value of four(4) byte integer + + @param buffer: (bytes) the source buffer + @param index: (int) the start index of reading the source buffer + @param signed: (bool) convert to signed or unsigned value + @return: pair of the [value and incremented index by the length. + """ + length = 4 # for an integer + value = bytearray_to_value("i" if signed else "I", buffer[index:index + length]) + return value, index + length + + +def bytearray_to_long(buffer: bytes, index: int, signed: bool = True) -> [int, int]: + """ + Converts the buffer of bytes to a value of eight(8) byte integer + + @param buffer: (bytes) the source buffer + @param index: (int) the start index of reading the source buffer + @param signed: (bool) convert to signed or unsigned value + @return: pair of the [value and incremented index by the length. + """ + length = 8 # for a long + value = bytearray_to_value("q" if signed else "Q", buffer[index:index + length]) + return value, index + length + + +def bytearray_to_float(buffer: bytes, index: int, is_double: bool = False) -> [float, int]: + """ + Converts the buffer of bytes to a value of floating point. + + @param buffer: (bytes) the source buffer + @param index: (int) the start index of reading the source buffer + @param is_double: (bool) convert the value to eight(8) byte floating point if is_double is True, + or four(4) bytes otherwise. + @return: pair of the [value and incremented index by the length. + """ + length = 8 if is_double else 4 + value = bytearray_to_value("d" if is_double else "f", buffer[index:index + length]) + return value, index + length + Index: leahi_dialin/utils/data_logger.py =================================================================== diff -u --- leahi_dialin/utils/data_logger.py (revision 0) +++ leahi_dialin/utils/data_logger.py (revision 2138d06d100fdcf23f2e9069f35ee2fdee62008f) @@ -0,0 +1,161 @@ +########################################################################### +# +# Copyright (c) 2021-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 data_logger.py +# +# @author (last) Quang Nguyen +# @date (last) 07-Jul-2021 +# @author (original) Peter Lucia +# @date (original) 30-Apr-2021 +# +############################################################################ +import glob +import os +import shutil +import threading +from collections import deque +from logging import Logger +from time import sleep +from typing import Tuple +from multiprocessing import Process +from pathlib import Path +import pandas as pd + +from leahi_dialin.utils.base import _FauxLogger + + +class DataLogger: + + MAX_CHUNK_SIZE = 20 + + def __init__(self, + folder: str = "/tmp/", # must be the full path + logger: Logger = _FauxLogger()): + """ + Logs data to xlsx file + @param folder: (str) the destination filepath + + """ + super().__init__() + self.queue = deque() # Thread safe + self.disable_logging = False + self.thread = threading.Thread(target=self.logging_scheduler, daemon=True) + self.base_folder = folder + self.csv_writers = {} + self.logger = logger + self.thread.start() + self.path_disable_logging = "/tmp/DIALIN_DISABLE_LOGGING" + + def logging_scheduler(self) -> None: + """ + Called on each timer event. Calls the data logger method + + @return: None + """ + while True: + self.do_data_logging() + sleep(0.05) + + def add_data(self, data: Tuple): + """ + + @param data: (Tuple) the data to add to the queue + @return: None + """ + # ("module", "timestamp", "header", "value") + if not os.path.exists(self.path_disable_logging): + self.queue.append(data) + # TODO: Remove later - keep for debugging + # else: + # self.logger.debug("Ignoring {0}".format(data)) + + def do_data_logging(self) -> None: + """ + Called on teach timer event. Logs the data currently in deque + @return: None + """ + + i = 0 + while self.queue: + module, timestamp, data_name, data_value = self.queue.pop() + filename = module + folder = os.path.join(self.base_folder, filename + "Log") + if not os.path.isdir(folder): + os.mkdir(folder) + filepath = os.path.join(folder, data_name + ".csv") + + if not os.path.exists(filepath): + header = ["timestamp", data_name] + with open(filepath, 'w') as f: + f.write(",".join(header) + "\n") + else: + with open(filepath, 'a') as f: + f.write(timestamp + ", " + data_value + "\n") + + i += 1 + if i >= self.MAX_CHUNK_SIZE: + break + + def export_to_xlsx(self, output_path: str) -> None: + """ + Called when the user wishes to export all captured logs to a xlsx file + + @param output_path: (str) the destination output path + @return: None + """ + process = Process(target=self._do_export_to_xlsx, args=(output_path,)) + process.start() + + def _do_export_to_xlsx(self, output_path: str) -> None: + """ + Performs the actual export to xlsx + + @param output_path: (str) the destination output path + @return: None + """ + + Path(self.path_disable_logging).touch() + self.logger.debug("Starting data export to {0}".format(output_path)) + log_path = os.path.join(self.base_folder, "*Log") + folders = glob.glob(log_path) + if len(folders) == 0: + self.logger.debug("No folder with data to export") + os.remove(self.path_disable_logging) + return + writer = pd.ExcelWriter(output_path) + for folder in folders: + files = os.path.join(folder, "*.csv") + csv_files = glob.glob(files) + module_name = os.path.basename(folder) + df = pd.DataFrame() + for csv_file in csv_files: + df_data = pd.read_csv(csv_file) + df_data = df_data.set_index("timestamp") + df = df.join(df_data, how="outer") + try: + df.to_excel(writer, sheet_name=module_name) + self.logger.debug("Added {0} to {1}".format(module_name, output_path)) + except ValueError as e: + self.logger.error("Error during write to excel: {0}".format(e)) + + writer.save() + self.logger.debug("Finished data export to {0}".format(output_path)) + os.remove(self.path_disable_logging) + + def clear_logs(self): + """ + Called when the user clears the logs + @return: None + """ + Path(self.path_disable_logging).touch() + log_path = os.path.join(self.base_folder, "*Log") + folders = glob.glob(log_path) + for folder in folders: + self.logger.debug("Removing {0}".format(folder)) + shutil.rmtree(folder) + os.remove(self.path_disable_logging) + Index: leahi_dialin/utils/excel_ops.py =================================================================== diff -u --- leahi_dialin/utils/excel_ops.py (revision 0) +++ leahi_dialin/utils/excel_ops.py (revision 2138d06d100fdcf23f2e9069f35ee2fdee62008f) @@ -0,0 +1,228 @@ +########################################################################### +# +# Copyright (c) 2021-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 excel_ops.py +# +# @author (last) Dara Navaei +# @date (last) 22-Feb-2022 +# @author (original) Dara Navaei +# @date (original) 21-Feb-2021 +# +############################################################################ + +import os +import math +import datetime +from openpyxl.styles import PatternFill, Font, Alignment, Protection +from openpyxl.utils import get_column_letter +from openpyxl import Workbook, load_workbook + +COLUMN_WIDTH_TOLERANCE = 3.0 +COLUMN_MAX_WIDTH = 50.0 +ROW_DEFAULT_HEIGHT = 15.0 +GREEN = PatternFill(start_color='00FF00', end_color='00FF00', fill_type='solid') +YELLOW = PatternFill(start_color='FFFF00', end_color='FFFF00', fill_type='solid') +BLUE = PatternFill(start_color='00CCFF', end_color='00CCFF', fill_type='solid') +RED = PatternFill(start_color='FF0000', end_color='FF0000', fill_type='solid') +NO_COLOR = PatternFill(fill_type='none') + + +def get_an_excel_workbook(): + """ + This function returns an excel workbook object + + @return excel workbook object + """ + return Workbook() + + +def setup_excel_worksheet(workbook_obj, title, index=None, protection=False): + """ + Creates the worksheets in the created excel file with the name of each software_project_name as the name of + the worksheet. + + @param workbook_obj: Excel workbook object + @param title: Title of the created worksheet + @param index: Index of the worksheet. If a sheet needs to be at a certain place (default none) + @param protection: Flag to indicate whether the worksheet is write protected or not (default False) + + @return none + """ + # Assume the new sheet does not exist + is_sheet_name_available = False + + # Loop through the list of the sheet names in the excel + for sheet_name in workbook_obj.sheetnames: + # If the requested title exists in the excel document, set as True + # and break out of the loop. + if title == sheet_name: + is_sheet_name_available = True + break + # If the requested sheet does not exist, create it + if is_sheet_name_available is False: + # Create a tab and name the tab with the name of the projects dictionary + workbook_obj.create_sheet(title=title, index=index) + + # Check if the created worksheet must be write protected and if yes, protect it + if protection: + workbook_obj[title].protection.sheet = True + + # If the first tab is Sheet or Sheet1, remove it + # The other tabs must be created first before removing the default tab + if workbook_obj.sheetnames[0] == 'Sheet' or workbook_obj.sheetnames[0] == 'Sheet1': + sheet = workbook_obj.get_sheet_by_name(workbook_obj.sheetnames[0]) + workbook_obj.remove_sheet(sheet) + + +def write_to_excel(workbook_obj, project, row, column, data, name='Calibri', font=11, bold=False, color=None, + merge=None, horizontal='left', freeze=False, max_col_len=None, protect_cell=False): + """ + This function writes data at the specified row and column in an excel file (object). + + @param workbook_obj: Excel workbook object + @param project: Name of the sheet (i.e HD-DEN-4308) + @param row: Current row number + @param column: Current column number + @param data: Data to be written at the cell + @param name: Font type (default Calibri) + @param font: Font size (default 11) + @param bold: Bold or un-bold (default un-bold) + @param color: Color of the cell (default no color) + @param merge: Merge cells. Cells must be provided with A1:A4 format (default none) + @param horizontal: Horizontal alignment (default left) + @param freeze: Freeze top row (default false) + @param max_col_len: maximum length of a column (default none, means it is not restricted) + @param protect_cell: flag to indicate whether to write protect cell or not (default False) + + @return: None + """ + row_height = 0 + # Get the number of an alphabetic column (i.e A -> 1) + column_letter = get_column_letter(column) + cell_name = column_letter + str(row) + + # Set the active worksheet to the provided worksheet + active_sheet = workbook_obj[project] + # Set the provided data into the specified row and column and set the bold, color and horizontal alignment + active_sheet.cell(row=row, column=column).value = data + active_sheet.cell(row=row, column=column).font = Font(size=font, bold=bold, name=name) + # Wrapping text is not needed unless the length of the data is more than maximum column + # length + active_sheet[cell_name].alignment = Alignment(vertical='center', horizontal=horizontal, wrap_text=False) + # Get the width of the current column + column_width = active_sheet.column_dimensions[column_letter].width + # When the column width is on the default, openpyxl reports None. If the width is reported as None, + # it will be set to 0 for math comparison + column_width = 0 if column_width is None else column_width + # If the length is not provided, use the default maximum length + max_len = COLUMN_MAX_WIDTH if max_col_len is None else max_col_len + # Remove all the end of the line artifacts + length_of_data = len(str(data).rstrip()) + + # If the length of data was greater than the maximum length, calculate the number of + # rows is needed with the + # default height + if length_of_data > max_len: + # Since the length is greater than maximum, enable wrap text + active_sheet[cell_name].alignment = Alignment(vertical='center', horizontal=horizontal, wrap_text=True) + # Calculate what the row height should be when the cell is extended + row_height = math.ceil(length_of_data / max_len) * ROW_DEFAULT_HEIGHT + active_sheet.column_dimensions[column_letter].width = max_len + # If the length of the data provided is already less than the length of cell, + # do nothing + elif column_width < length_of_data: + active_sheet.column_dimensions[column_letter].width = length_of_data + COLUMN_WIDTH_TOLERANCE + row_height = ROW_DEFAULT_HEIGHT + + # If the current row height is not defined or the row height is less than the calculated new height, set it + if active_sheet.row_dimensions[row].height is None or active_sheet.row_dimensions[row].height < row_height: + active_sheet.row_dimensions[row].height = row_height + + # If color has been defined, set the color of the cell + if color is not None: + active_sheet[cell_name].fill = color + + # If merge has been requested + if merge is not None: + # The format of merge for this function is A1:C1 + active_sheet.merge_cells(str(merge)) + + if freeze: + # To freeze row 1, make the cell is not row 1, that's why A2 was chosen + active_sheet.freeze_panes = 'A2' + + # Enforce the cell protection whether it is a False or True + active_sheet.cell(row, column).protection = Protection(locked=protect_cell) + + +def load_excel_report(path): + """ + This function returns an object of a currently existing excel workbook + + @return loaded excel workbook object + """ + return load_workbook(path) + + +def get_cell_value(workbook_obj, project, row, col): + """ + This function returns the value of a written excel cell + + @return excel workbook object + """ + # Set the active worksheet to the provided worksheet + active_sheet = workbook_obj[project] + # Get the cell object + cell_obj = active_sheet.cell(row=row, column=col) + # Convert it to the actual value + return cell_obj.value + + +def merge_cells(workbook_obj, project, start_row, start_col, end_row, end_col): + """ + This function merges the specified cells. + + @param workbook_obj: Excel workbook object + @param project: Name of the sheet (i.e HD-DEN-4308) + @param start_row: Row number at the beginning of merge + @param start_col: Column number at the beginning of merge + @param end_row: Row number at the end of merge + @param end_col: Column number at the end of merge + + @return: None + """ + # Go to the define sheet + active_sheet = workbook_obj[project] + # Convert the cell numbers to text and number (i.e row=1 and col=1 -> A1) for the start and end cells + merge_start_cell = get_column_letter(start_col) + str(start_row) + merge_end_cell = get_column_letter(end_col) + str(end_row) + # The format of merge for this function is A1:C1 + active_sheet.merge_cells(str(merge_start_cell) + ':' + str(merge_end_cell)) + + +def save_report(excel_workbook, save_dir, record_name, stack_name=None): + """ + This function overrides the save function in the Base class. The function saves the excel file. + + @param excel_workbook: Excel workbook object + @param save_dir: Saving directory + @param record_name: Type of record being saved (i.e. calibration, software configuration) + @param stack_name: Name of the software stack name (i.e. HD) default none + + @returns none + """ + # Get the current date + current_date = str(datetime.datetime.now().date()) + # Some of the records might want to add the name of the stack. For instance, software configurations might want to + # mention that this is an HD or DG software configuration report. + if stack_name is not None: + address = current_date + '-' + str(stack_name) + '-' + str(record_name).upper() + '-Record.xlsx' + else: + address = current_date + '-' + str(record_name).upper() + '-Record.xlsx' + # Create the save path by using the path, date and current code review excel count out of total number of them + path = os.path.join(save_dir, address) + excel_workbook.save(filename=path) Index: leahi_dialin/utils/helpers.py =================================================================== diff -u --- leahi_dialin/utils/helpers.py (revision 0) +++ leahi_dialin/utils/helpers.py (revision 2138d06d100fdcf23f2e9069f35ee2fdee62008f) @@ -0,0 +1,238 @@ +########################################################################### +# +# Copyright (c) 2021-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 helpers.py +# +# @author (last) Quang Nguyen +# @date (last) 07-Jul-2021 +# @author (original) Peter Lucia +# @date (original) 29-Apr-2021 +# +############################################################################ +import logging +import subprocess +from datetime import datetime +from .base import LogManager + + +def setup_virtual_can_interface(): + """ + Convenience function to setup a virtual can interface using the most common settings + See the setup_can function to setup a can interface with custom settings. + + @return: True if successful, false otherwise + """ + bring_interface_down(interface="can0", delete=True) + is_present = is_interface_present("can0") + if not is_present: + setup_can(interface="can0", + is_virtual=True) + result = is_interface_up("can0") + if result: + print("Success") + return True + + print("Failure") + return False + + +def setup_real_can_interface(): + """ + Convenience function to setup a real can interface using the most common settings + See the setup_can function to setup a can interface with custom settings. + + @return: True if successful, False otherwise + """ + delete_virtual = input("Delete virtual can interface? (y,n)") + if delete_virtual.upper() in ["Y", "YES"]: + bring_interface_down(interface="can0", delete=True) + if is_interface_present("can0"): + print("Failure") + return False + else: + bring_interface_down(interface="can0", delete=False) + if not is_interface_present("can0") or is_interface_up("can0"): + print("Failure") + return False + input("Press 'Enter' after plugging in the PCAN-USB.") + setup_can(interface="can0", + is_virtual=False, + bitrate=250000, + restart_ms=100, + txqueue=10000) + + if is_interface_present("can0") and is_interface_up("can0"): + print("Success") + return True + else: + print("Failure") + return False + + +def bring_interface_down(interface: str = "can0", delete=False): + """ + Brings the specified can interface down + + @param interface: (str) the interface name + @param delete: (bool) whether to delete the interface after bringing it down + @return: None + """ + cmd_iface_down = "sudo ifconfig {0} down > /dev/null 2>&1".format(interface) + cmd_iface_delete = "sudo ip link delete dev {0} > /dev/null 2>&1".format(interface) + + success, info = run_cmd(cmd_iface_down) + if not success: + print("Warning: Could not bring interface down: {0}".format(info)) + if delete: + run_cmd(cmd_iface_delete) + if not success: + print("Warning: Could not delete the interface: {0}".format(info)) + + +def run_cmd(cmd: str): + """ + Runs the provided command, returns if it was successful and output or error information + + @param cmd: (str) the command to run + @return: tuple(bool, info) Whether the command was successful. If true info is the output, otherwise + info contains the error information + """ + try: + result = subprocess \ + .check_output(cmd, + shell=True).decode("utf-8").strip() + return True, result + except subprocess.CalledProcessError as e: + return False, str(e) + + +def is_interface_up(interface: str = "can0"): + """ + Checks if the specified interface is listed and up + + @param interface: + @return: (bool) True if up, False if not or if we can't tell + """ + cmd_check_interface = "ifconfig {0}".format(interface) + + success, info = run_cmd(cmd_check_interface) + if success: + if interface in info and "UP,RUNNING" in info: + return True + return False + + +def is_interface_present(interface: str = "can0"): + """ + Checks if the specified interface is listed + + @param interface: + @return: (bool) True if present, False if not or if we can't tell + """ + cmd_check_interface = "ip a" + + success, info = run_cmd(cmd_check_interface) + if success: + if interface in info: + return True + return False + + +def setup_can(interface: str = "can0", is_virtual=False, bitrate=250000, restart_ms=100, txqueue=10000): + """ + Convenience function to setup a can interface + + @param interface: (str) The interface name + @param is_virtual: (bool) If the interface is virtual or not + @param bitrate: (int) The desired bitrate (applies to non-virtual interfaces only) + @param restart_ms: (int) The desired restart_ms (applies to non-virtual interfaces only) + @param txqueue: (int) The desired txqueue length + @return: + """ + cmd_iface_up = "sudo ip link set {0} up type can bitrate {1} restart-ms {2}" \ + .format(interface, bitrate, restart_ms) + cmd_txqueue = "sudo ifconfig {0} txqueuelen {1}".format(interface, txqueue) + + cmd_iface_type_virtual = "sudo ip link add dev {0} type vcan".format(interface) + cmd_iface_up_virtual = "sudo ip link set {0} up".format(interface) + + if is_virtual: + success, info = run_cmd(cmd_iface_type_virtual) + if not success: + print("Warning: Could not set iface type to virtual: {0}".format(info)) + success, info = run_cmd(cmd_iface_up_virtual) + if not success: + print("Warning: Could not bring up virtual interface: {0}".format(info)) + else: + success, info = run_cmd(cmd_iface_up) + if not success: + print("Warning: Could not bring interface up: {0}".format(info)) + + success, info = run_cmd(cmd_txqueue) + if not success: + print("Warning: Could not set txtqueue length: {0}".format(cmd_txqueue)) + + +def create_logger(log_path: str = "/tmp/DialinScript.log", + level: str = "ERROR", + enable_metadata=True, + clear_before_write=False): + """ + Convenience function to create a logger for external Dialin scripts + + @param log_path: (str) The full path to the output log file. + @param level: (str) The logging level (e.g. INFO, WARN, DEBUG, ERROR, CRITICAL) + @param enable_metadata: (bool) if True, include metadata, otherwise, log only the message + @param clear_before_write: (bool) if True, clear log file before write to it + @return: (logging.Logger) The logger object + """ + + numeric_level = getattr(logging, level, logging.ERROR) + + current_time = datetime.now() + + # create a new unique logger using current date and time + logger = logging.getLogger("DialinScript{0}{1}{2}".format(current_time.hour, + current_time.minute, + current_time.second)) + logger.setLevel(numeric_level) + + if clear_before_write: + fh = logging.FileHandler(log_path, mode='w') + else: + fh = logging.FileHandler(log_path) + fh.setLevel(numeric_level) + ch = logging.StreamHandler() + ch.setLevel(numeric_level) + if enable_metadata: + formatter = logging.Formatter(fmt=LogManager.LOG_FMT, + datefmt=LogManager.LOG_DT_FMT) + else: + formatter = logging.Formatter(fmt=LogManager.LOG_FMT_NO_METADATA) + fh.setFormatter(formatter) + ch.setFormatter(formatter) + + logger.addHandler(fh) + logger.addHandler(ch) + return logger + + +def find_variables_in_object(obj, value, starts_with: str = ""): + """ + Returns a list of variable names that in the object that match the searched value + + @param obj: (object) The object to search through + @param value: (object) The value to lookup + @param starts_with: (str) The + @return: (List[str]) A list of variable names matching the searched value + """ + result = [] + for attr in dir(obj): + if not callable(getattr(obj, attr)) and attr.startswith(starts_with): + if value == getattr(obj, attr): + result.append(attr) + return result Index: leahi_dialin/utils/nv_ops_utils.py =================================================================== diff -u --- leahi_dialin/utils/nv_ops_utils.py (revision 0) +++ leahi_dialin/utils/nv_ops_utils.py (revision 2138d06d100fdcf23f2e9069f35ee2fdee62008f) @@ -0,0 +1,1038 @@ +########################################################################### +# +# Copyright (c) 2021-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 nv_ops_utils.py +# +# @author (last) Dara Navaei +# @date (last) 05-Mar-2024 +# @author (original) Dara Navaei +# @date (original) 21-Feb-2021 +# +############################################################################ +import os.path +import struct +import time +from enum import unique +from logging import Logger +from typing import List +from collections import OrderedDict +from .excel_ops import * +from leahi_dialin.utils.base import AbstractObserver, DialinEnum + + +@unique +class NVRecordsDG(DialinEnum): + NVDATAMGMT_CALIBRATION_RECORD = 0 + NVDATAMGMT_SYSTEM_RECORD = 1 + NVDATAMGMT_SERVICE_RECORD = 2 + NVDATAMGMT_SCHEDULED_RUNS_RECORD = 3 + NVDATAMGMT_HEATERS_INFO_RECORD = 4 + NVDATAMGMT_USAGE_INFO_RECORD = 5 + NVDATAMGMT_SW_CONFIG_RECORD = 6 + NUM_OF_NVDATMGMT_RECORDS_JOBS = 7 + +class NVRecordsHD(DialinEnum): + NVDATAMGMT_CALIBRATION_RECORD = 0 + NVDATAMGMT_SYSTEM_RECORD = 1 + NVDATAMGMT_SERVICE_RECORD = 2 + NVDATAMGMT_INSTITUTIONAL_RECORD = 3 + NVDATAMGMT_USAGE_INFO_RECORD = 4 + NVDATAMGMT_SW_CONFIG_RECORD = 5 + NUM_OF_NVDATMGMT_RECORDS_JOBS = 6 + + +class NVUtilsObserver(AbstractObserver): + """ + + Observation class + """ + def __init__(self, prop): + self.received = False + self.prop = prop + + def update(self, message): + """ + Publicly accessible function to provide an update of the object that is being observed + + @param message: (str) the message to update its status + + @return none + """ + self.received = message.get(self.prop, False) + + +class NVOpsUtils: + """ + + Processes the calibration_record records, service records, system records, and the scheduled runs records. + The records are prepared to be sent to firmware or to be received from firmware. + """ + CRC_16_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 + ) + + # Public defines + DEFAULT_CHAR_VALUE = ' ' + CAL_RECORD_TAB_NAME = 'Calibration_Record' + NON_VOLATILE_RECORD_NAME = 'SW_Config_Report' + USAGE_INFO_RECORD_TAB_NAME = 'Usage_Info_Record' + SYSTEM_RECORD_TAB_NAME = 'System_Record' + SERVICE_RECORD_TAB_NAME = 'Service_Record' + INSTITUTIONAL_RECORD_TAB_NAME = 'Institutional_Record' + DEFAULT_EPOCH_VALUE = 0 + + _RECORD_START_INDEX = 6 + _RECORD_SPECS_BYTES = 12 + _RECORD_SPECS_BYTE_ARRAY = 3 + + _CURRENT_MESSAGE_NUM_INDEX = 0 + + _DATA_TYPE_INDEX = 0 + _DATA_VALUE_INDEX = 1 + _CHAR_LENGTH_INDEX = 2 + _DICT_VALUE_LIST_LEN = 2 + + _TARGET_BYTES_TO_SEND_TO_FW = 150 + _MIN_PAYLOAD_BYTES_SPACE = 4 + + _PAYLOAD_CURRENT_MSG_INDEX = 0 + _PAYLOAD_TOTAL_MSG_INDEX = 1 + _PAYLOAD_TOTAL_BYTES_INDEX = 2 + + _SW_CONFIGS_TITLE_COL = 'SW Configurations' + _SW_CONFIGS_VALUE_COL = 'Status' + _SW_CONFIGS_REPORT_NAME = 'SW-CONFIGS' + _CAL_TIME_NAME = 'cal_time' + _NEW_CAL_TIME_SIGNAL = 'new' + _CRC_NAME = 'crc' + _PADDING_GROUP_NAME = 'padding' + + def __init__(self, logger: Logger): + """ + Constructor for the NVOptsUtils class + + @param logger: (Logger) the logger + """ + + self.logger = logger + self._workspace_dir = '' + self._excel_workbook = '' + self._record_name = '' + self._firmware_stack = '' + self._is_writing_to_excel_done = False + self._is_read_done = False + + # This list contains different data packet lists + # i.e. [[message 1, payload], [message 2, payload], ...] + self._record_packets_to_send_to_fw = [] + + # Buffer that is used to keep the groups data except the crc to convert them to bytes and calculate the crc + # of the group. + self._temp_groups_data_to_calculate_crc = [] + + def _create_workspace(self, dir_name): + """ + Publicly accessible function to get create a workspace for the script that is running. + + @param dir_name: Name of the workspace directory + + @return none + """ + # Get the root directory of the current script + scripts_root_dir = os.path.dirname(os.path.dirname(__file__)) + # Get the root directory of the entire scripts folder. The workspace that holds the + # code review reports and clones other scripts and repositories must be outside of the scripts + root_dir = os.path.dirname(os.path.dirname(scripts_root_dir)) + # Create the address of the workspace + self._workspace_dir = os.path.join(root_dir, dir_name) + # If the path does not exist, make it, otherwise, change to that directory + if not os.path.isdir(self._workspace_dir): + # Create the directory and go to it + os.mkdir(self._workspace_dir) + os.chdir(self._workspace_dir) + + def prepare_excel_report(self, firmware_stack: str, record_name: str, directory: str, protect_sheet: bool = False): + """ + Publicly accessible function to prepare the excel report + + @param firmware_stack: (str) firmware stack name (e.g. "HD" or "DG") + @param record_name: (str) record type to check such as calibration, system, ... + @param directory: (str) the directory in which to write the excel document. + @param protect_sheet: (bool) flag to indicate whether to write protect the sheet or not (default False) + @return none + """ + path = '' + is_report_found = False + + # If a directory is provided and there is not a folder in that address, create the + # directory. Set the workspace directory to the provided directory. If a directory was not + # provided, create a workspace in the default position + default_nv_directory = firmware_stack + '_NV_Records' + + if directory is not None: + directory = os.path.join(directory, default_nv_directory) + if not os.path.isdir(directory): + # Create the directory and go to it + os.mkdir(directory) + + self._workspace_dir = directory + os.chdir(self._workspace_dir) + else: + self._create_workspace(default_nv_directory) + + self._record_name = record_name + self._firmware_stack = firmware_stack + + # List all the files in the workspace directory + for file in os.listdir(self._workspace_dir): + # If the file has an extension of .xlsx + if file.endswith('.xlsx'): + # Check if the firmware stack (i.e. DG) is in the file and name of the file + # does not have lock in it. When the file is open, there is a hidden lock file + # in there and it is ignored + file = str(file) + if self._firmware_stack in file and 'lock' not in file: + if str(datetime.datetime.now().date()) in file: + if self._SW_CONFIGS_REPORT_NAME in file and self.NON_VOLATILE_RECORD_NAME == record_name: + # Create the file path and exit the loop + path = os.path.join(self._workspace_dir, file) + is_report_found = True + break + elif self._SW_CONFIGS_REPORT_NAME not in file and self.NON_VOLATILE_RECORD_NAME not in \ + record_name: + # Create the file path and exit the loop + path = os.path.join(self._workspace_dir, file) + is_report_found = True + break + + if is_report_found: + # Load the excel workbook + self._excel_workbook = load_excel_report(path) + else: + # Get an excel workbook object + self._excel_workbook = get_an_excel_workbook() + + # Setup worksheet and create the current tab + setup_excel_worksheet(self._excel_workbook, self._record_name, protection=protect_sheet) + + def write_fw_record_to_excel(self, calibration_record: OrderedDict): + """ + Writes a calibration record to excel + @param calibration_record: (dict) the record to write to excel + @return: None + """ + try: + row = 1 + # Let's say the calibration record is: + # Get the keys of the calibration group {'pressure_sensors': 'ppi', {'fourth_order': [' bytearray: + """ + Gets the list of the characters and makes sure their length is to the define length + + @param record: (list) the list that contains the characters, data type and the target character length + @return characters (bytearray) that are converted to bytearrays + """ + data_type = record[0] + char = record[1] + char_len = record[2] + temp = bytearray() + + if len(char) > char_len: + char = char[:char_len] + elif len(char) < char_len: + for i in range(len(char), char_len): + char += NVOpsUtils.DEFAULT_CHAR_VALUE + + for ch in char: + temp += struct.pack(data_type, ch.encode('ascii')) + + return temp + + @staticmethod + def reset_fw_record(record: OrderedDict) -> OrderedDict: + """ + Gets a record and updated the calibration date and crc + + @param record: (dict) the record to calculate the calibration time and crc + @return record (OrderedDict) the record with updated calibration time and crc + """ + for key, value in record.items(): + if isinstance(value, dict): + for sub_key, sub_value in value.items(): + if sub_key == 'cal_time': + sub_value[sub_key][1] = NVOpsUtils.get_current_time_in_epoch() + crc = NVOpsUtils.get_group_record_crc(sub_value) + + sub_value['crc'][1] = crc + return record + + @staticmethod + def reset_fw_system_service_record(record: OrderedDict) -> OrderedDict: + """ + Gets a record and updated the calibration date and crc + + @param record: (dict) the record to calculate the calibration time and crc + @return record (OrderedDict) the record with updated calibration time and crc + """ + for key, value in record.items(): + # Check if there is a CRC in the inner dictionary since some of the structures might not have it. + # For instance, the software configuration record does not have an inner CRC and it only has a global CRC + # with the padding + if isinstance(value, dict): + crc = NVOpsUtils.get_group_record_crc(value) + value['crc'][1] = crc + return record + + @staticmethod + def get_group_record_crc(group_record: dict) -> int: + """ + Gets a group record and calculates the crc for the group + + @param group_record: (dict) the record to calculate the crc + @return crc (int) the calculated crc + """ + value_in_bytes = b'' + temp = [] + for key, value in group_record.items(): + if key != 'crc': + data_type = value[0] + if data_type == ' self._MIN_PAYLOAD_BYTES_SPACE: + current_payload_length += data_type_bytes + temp_buffer[self._PAYLOAD_TOTAL_MSG_INDEX] = struct.pack(' self._MIN_PAYLOAD_BYTES_SPACE: + current_payload_length += data_type_bytes + # Insert a 4-byte 0 to the index of the total messages. This is a place holder and it will + # be updated with the right value later. + temp_buffer[self._PAYLOAD_TOTAL_MSG_INDEX] = struct.pack(' self._DICT_VALUE_LIST_LEN: + byte_size += current_byte_size * group[key][self._CHAR_LENGTH_INDEX] + else: + byte_size += current_byte_size + + return byte_size + + @classmethod + def crc_16(cls, data): + """ + generates crc16 for the provided data + @param data: byte of data + @return: (int) the crc code + """ + crc = 0xFFFF + length = len(data) + i = 0 + + while length > 0: + # Make the sure variables are 16-bit integers + left = (crc << 8) & 0x0000FFFF + right = (crc >> 8) & 0x0000FFFF + crc = left ^ cls.CRC_16_TABLE[data[i] ^ (right & 0x00FF)] + length -= 1 + i += 1 + + return crc + + @staticmethod + def get_data_type_bytes(data): + """ + Handles converting the string representation of the bytes of the data types in a struct to numbers. + This is a static method. + + @param data: the data to be converted to bytes in number + @return: calculated byte size + """ + number_of_bytes = struct.calcsize(data) + + return number_of_bytes + + @staticmethod + def calculate_padding_byte_size(total_byte_size, max_buffer_size): + """ + Handles calculating the padding length based on the provided buffer sizes. This is a static method. + + @param total_byte_size: total byte size of a record dictionary + @param max_buffer_size: max buffer size that is allowed to be used in the dictionary + + @return: padding size in bytes + """ + + # Calculate the padding size: + # If bytes in the dictionary % max write bytes to RTC RAM (64) or EEPROM (16) is 0, not padding is needed + # Else padding = (ceil(dictionary bytes/max write) * max write)-dictionary bytes + if (total_byte_size % max_buffer_size) == 0: + padding_size = 0 + else: + padding_size = (math.ceil(total_byte_size / max_buffer_size) * max_buffer_size) - total_byte_size + + return padding_size + + def write_sw_config_to_excel(self, sw_configs: OrderedDict, stack_name: str): + """ + Publicly accessible function to write the software configurations into an excel report + + @param sw_configs: (ordered dictionary) the configurations record to write to excel + @param stack_name: (str) the name of the software stack (i.e. HD) + + @return none + """ + row = 1 + names_col_number = 2 + write_to_excel(self._excel_workbook, self._record_name, row, names_col_number, self._SW_CONFIGS_TITLE_COL, + bold=True, freeze=True, protect_cell=True) + + values_col_number = 3 + write_to_excel(self._excel_workbook, self._record_name, row, values_col_number, self._SW_CONFIGS_VALUE_COL, + bold=True, protect_cell=True) + + # Prepare for writing the values + row += 1 + + for key, values in sw_configs['sw_configs'].items(): + + write_to_excel(self._excel_workbook, self._record_name, row, names_col_number, key, bold=True, + protect_cell=True) + + # Get the configuration value + config_value = values[1] + # If the config value is not 0, color the cell as green otherwise, leave it none colored + color = GREEN if config_value != 0 else NO_COLOR + write_to_excel(self._excel_workbook, self._record_name, row, values_col_number, config_value, color=color) + row += 1 + + save_report(self._excel_workbook, self._workspace_dir, self._SW_CONFIGS_REPORT_NAME, stack_name=stack_name) + + def get_sw_configs_from_excel(self, sw_configs_dict: OrderedDict, excel_path: str, sw_config_excel_tab: str): + """ + Publicly accessible function to get the software configurations from excel + + @param sw_configs_dict: (ordered dictionary) the configurations record to write to excel + @param excel_path: (str): the path to the excel report that its data is read + @param sw_config_excel_tab: (str): the name of the tab in the excel report that the values are located at + + @return status of the operations + """ + row = 1 + col = 1 + title_col = None + value_col = None + max_col_to_go = 50 + status = False + # Load the excel report + self._excel_workbook = load_excel_report(excel_path) + active_sheet = self._excel_workbook[sw_config_excel_tab] + + while True: + # Loop through the cells in the title row + value = active_sheet.cell(row=row, column=col).value + # If the col number exceeded the maximum column number, break out of the loop + if col >= max_col_to_go: + break + + # Check if the value of the read cell is not none + if value is not None: + # If the value of the title row is name of the sw configs title column name, update the title col number + if value.strip() == self._SW_CONFIGS_TITLE_COL: + title_col = col + # If the value of the title row is the name of the values of the sw configs, update the value col number + if value.strip() == self._SW_CONFIGS_VALUE_COL: + value_col = col + # If the title col and value col numbers are both found and they are not none, then exit the while loop + # since the values have been found, otherwise, increment the column number and keep looking + if title_col is not None and value_col is not None: + break + else: + col += 1 + + if title_col is not None and value_col is not None: + # Get the last non-empty row number of the current active sheet + last_non_empty_row = active_sheet.max_row + # Get the dictionary of the provided sw configurations dictionary, this can be either HD or DG + fw_sw_configs = sw_configs_dict['sw_configs'] + # Loop through the excel from row 2 since row 1 is the titles row until the last non-empty row + 1 since the + # range method does not include that last element so the last non-empty row will not be covered if there is + # not a +1. + for row in range(2, last_non_empty_row + 1): + config = active_sheet.cell(row=row, column=title_col).value + + if config is not None: + # Check if the software configuration that has been read from excel exists in dictionary that has + # been prepared in Dialin + if config.strip() in fw_sw_configs: + excel_config_value = active_sheet.cell(row=row, column=value_col).value + # Check if the value is an integer and it is a 1 or a 0 + # The only acceptable values are 1 for enable and 0 for disable + if isinstance(excel_config_value, int) and excel_config_value == 1 or excel_config_value == 0: + fw_sw_configs[config.strip()][1] = excel_config_value + else: + # If the value is not acceptable, set the default value that is sent down to firmware to 0 + # and write incorrect into the report so the user will notice that they had and empty cell + # or a cell with a non-acceptable value (i.e. 123). Color the cell as red. + fw_sw_configs[config.strip()][1] = 0 + write_to_excel(self._excel_workbook, sw_config_excel_tab, row, value_col, 'Incorrect Value', + color=RED) + # Save back the excel workbook with the changes + # This save function is the openpyxl save and not the internal save function that creates + # the save path. The save path already exists + self._excel_workbook.save(filename=excel_path) + status = True + + return status Index: leahi_dialin/utils/singleton.py =================================================================== diff -u --- leahi_dialin/utils/singleton.py (revision 0) +++ leahi_dialin/utils/singleton.py (revision 2138d06d100fdcf23f2e9069f35ee2fdee62008f) @@ -0,0 +1,45 @@ +########################################################################### +# +# Copyright (c) 2021-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 singleton.py +# +# @author (last) Quang Nguyen +# @date (last) 19-Jul-2021 +# @author (original) Peter Lucia +# @date (original) 29-Apr-2021 +# +############################################################################ +from threading import Lock + + +class SingletonMeta(type): + """ + Thread-safe implementation of Singleton. + """ + + _instances = {} + + _lock: Lock = Lock() + _creation_attempts = 0 + """ + Synchronizes threads during first access to the Singleton. + """ + + def __call__(cls, *args, **kwargs): + """ + Possible changes to the value of the `__init__` argument do not affect + the returned instance. + """ + cls._creation_attempts += 1 + # TODO: Remove later - keep for debugging + # print("CAN interface constructor call counter: {0}".format(cls._creation_attempts)) + # First thread acquires the lock + with cls._lock: + if cls not in cls._instances: + instance = super().__call__(*args, **kwargs) + cls._instances[cls] = instance + return cls._instances[cls] Index: leahi_dialin/version.py =================================================================== diff -u --- leahi_dialin/version.py (revision 0) +++ leahi_dialin/version.py (revision 2138d06d100fdcf23f2e9069f35ee2fdee62008f) @@ -0,0 +1,69 @@ +########################################################################### +# +# 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 version.py +# +# @author (last) Dara Navaei +# @date (last) 18-Jan-2023 +# @author (original) Peter Lucia +# @date (original) 18-Jun-2020 +# +############################################################################ +import subprocess + +VERSION = "0.9.0" + + +def get_branch(): + """ + Gets the current branch name in the current git repository + + @return: The current branch name, None if it can't be determined + """ + + try: + return subprocess.check_output("git rev-parse --abbrev-ref HEAD", shell=True).decode("utf-8").strip() + except subprocess.CalledProcessError: + return None + + +def get_last_commit(): + """ + Gets the latest commit in the current git repository + + @return: (str) the latest commit in the current git repository, None if it can't be determined + """ + try: + return subprocess.check_output("git rev-parse --short=7 HEAD", shell=True).decode("utf-8").strip() + except subprocess.CalledProcessError: + return None + + +def check_if_git_repo(): + """ + Checks if we're in a git repo or not to know if we can get the git branch and commit + + @return: True if in a git repo, False otherwise + """ + + return subprocess.call(["git", "branch"], stderr=subprocess.DEVNULL, stdout=subprocess.DEVNULL) == 0 + + +branch = None +commit = None + +DEV_VERSION = VERSION + +if check_if_git_repo(): + branch = get_branch() + commit = get_last_commit() + DEV_VERSION += ".{0}".format(branch) + DEV_VERSION += ".{0}".format(commit) + + +if __name__ == '__main__': + print(VERSION) Index: setup.py =================================================================== diff -u -r07db7b5f01ad17d60c190e21574e1ed0552535ff -r2138d06d100fdcf23f2e9069f35ee2fdee62008f --- setup.py (.../setup.py) (revision 07db7b5f01ad17d60c190e21574e1ed0552535ff) +++ setup.py (.../setup.py) (revision 2138d06d100fdcf23f2e9069f35ee2fdee62008f) @@ -68,7 +68,7 @@ if __name__ == '__main__': check_if_git_repo() setuptools.setup( - name="leahi-dialin", + name="leahi_dialin", author="Peter Lucia", author_email="plucia@diality.com", description="The Diality Dialin API", Index: tests/dg_nvm_scripts.py =================================================================== diff -u -r07db7b5f01ad17d60c190e21574e1ed0552535ff -r2138d06d100fdcf23f2e9069f35ee2fdee62008f --- tests/dg_nvm_scripts.py (.../dg_nvm_scripts.py) (revision 07db7b5f01ad17d60c190e21574e1ed0552535ff) +++ tests/dg_nvm_scripts.py (.../dg_nvm_scripts.py) (revision 2138d06d100fdcf23f2e9069f35ee2fdee62008f) @@ -25,7 +25,7 @@ # Use cmd_get_dg_sw_config_record() to get the software configurations record in an excel # This function gets an address to locate the report there (i.e. /home/fw/projects/) # It creates a folder called DG_NV_Records in the destination that is called - # If no address is provided, the default location is one folder above the leahi-dialin folder wherever it is installed + # If no address is provided, the default location is one folder above the leahi_dialin folder wherever it is installed # in your computer. #dg.sw_configs.cmd_get_dg_sw_config_record() @@ -47,7 +47,7 @@ # Use cmd_get_dg_calibration_record_report() to get the calibration record in an excel # This function gets an address to locate the report there (i.e. /home/fw/projects/) # It creates a folder called DG_NV_Records in the destination that is called - # If no address is provided, the default location is one folder above the leahi-dialin folder wherever it is installed + # If no address is provided, the default location is one folder above the leahi_dialin folder wherever it is installed # in you computer. #dg.calibration_record.cmd_get_dg_calibration_record_report() @@ -66,7 +66,7 @@ # Use cmd_get_dg_system_record_report() to get the system record in an excel # This function gets an address to locate the report there (i.e. /home/fw/projects/) # It creates a folder called DG_NV_Records in the destination that is called - # If no address is provided, the default location is one folder above the leahi-dialin folder wherever it is installed + # If no address is provided, the default location is one folder above the leahi_dialin folder wherever it is installed # in your computer. dg.system_record.cmd_get_dg_system_record_report() @@ -82,7 +82,7 @@ # Use cmd_get_dg_system_record_report() to get the system record in an excel # This function gets an address to locate the report there (i.e. /home/fw/projects/) # It creates a folder called DG_NV_Records in the destination that is called - # If no address is provided, the default location is one folder above the leahi-dialin folder wherever it is installed + # If no address is provided, the default location is one folder above the leahi_dialin folder wherever it is installed # in your computer. dg.usage_record.cmd_get_dg_usage_info_record() @@ -97,7 +97,7 @@ # Use cmd_get_dg_system_record_report() to get the system record in an excel # This function gets an address to locate the report there (i.e. /home/fw/projects/) # It creates a folder called DG_NV_Records in the destination that is called - # If no address is provided, the default location is one folder above the leahi-dialin folder wherever it is installed + # If no address is provided, the default location is one folder above the leahi_dialin folder wherever it is installed # in your computer. #dg.service_record.cmd_get_dg_service_record() Index: tests/dg_tests.py =================================================================== diff -u -r07db7b5f01ad17d60c190e21574e1ed0552535ff -r2138d06d100fdcf23f2e9069f35ee2fdee62008f --- tests/dg_tests.py (.../dg_tests.py) (revision 07db7b5f01ad17d60c190e21574e1ed0552535ff) +++ tests/dg_tests.py (.../dg_tests.py) (revision 2138d06d100fdcf23f2e9069f35ee2fdee62008f) @@ -636,7 +636,7 @@ def run_flush_mode(): complete_counter = 1 - f = open("/home/fw/projects/leahi-dialin/tests/flush_mode.log", "w") + f = open("/home/fw/projects/leahi_dialin/tests/flush_mode.log", "w") dg.hd_proxy.cmd_start_stop_dg_flush() #dg.cmd_dg_software_reset_request() Index: tests/hd_blood_leak_data.py =================================================================== diff -u -r07db7b5f01ad17d60c190e21574e1ed0552535ff -r2138d06d100fdcf23f2e9069f35ee2fdee62008f --- tests/hd_blood_leak_data.py (.../hd_blood_leak_data.py) (revision 07db7b5f01ad17d60c190e21574e1ed0552535ff) +++ tests/hd_blood_leak_data.py (.../hd_blood_leak_data.py) (revision 2138d06d100fdcf23f2e9069f35ee2fdee62008f) @@ -119,7 +119,7 @@ print(hd.blood_leak.get_blood_leak_emb_mode_command_response()) """ """ - f = open("/home/fw/projects/leahi-dialin/tests/blood_leak.log", "w") + f = open("/home/fw/projects/leahi_dialin/tests/blood_leak.log", "w") try: while True: Index: tests/hd_nvm_scripts.py =================================================================== diff -u -r07db7b5f01ad17d60c190e21574e1ed0552535ff -r2138d06d100fdcf23f2e9069f35ee2fdee62008f --- tests/hd_nvm_scripts.py (.../hd_nvm_scripts.py) (revision 07db7b5f01ad17d60c190e21574e1ed0552535ff) +++ tests/hd_nvm_scripts.py (.../hd_nvm_scripts.py) (revision 2138d06d100fdcf23f2e9069f35ee2fdee62008f) @@ -26,7 +26,7 @@ # Use cmd_get_hd_sw_config_record() to get the software configurations record in an excel # This function gets an address to locate the report there (i.e. /home/fw/projects/) # It creates a folder called HD_NV_Records in the destination that is called - # If no address is provided, the default location is one folder above the leahi-dialin folder wherever it is installed + # If no address is provided, the default location is one folder above the leahi_dialin folder wherever it is installed # in your computer. #hd.sw_configs.cmd_get_hd_sw_config_record() @@ -48,7 +48,7 @@ # Use cmd_get_dg_calibration_record_report() to get the calibration record in an excel # This function gets an address to locate the report there (i.e. /home/fw/projects/) # It creates a folder called HD_NV_Records in the destination that is called - # If no address is provided, the default location is one folder above the leahi-dialin folder wherever it is installed + # If no address is provided, the default location is one folder above the leahi_dialin folder wherever it is installed # in your computer. hd.calibration_record.cmd_get_hd_calibration_record_report() @@ -67,7 +67,7 @@ # Use cmd_get_dg_system_record_report() to get the system record in an excel # This function gets an address to locate the report there (i.e. /home/fw/projects/) # It creates a folder called DG_NV_Records in the destination that is called - # If no address is provided, the default location is one folder above the leahi-dialin folder wherever it is installed + # If no address is provided, the default location is one folder above the leahi_dialin folder wherever it is installed # in your computer. #hd.system_record.cmd_get_hd_system_record_report() @@ -83,7 +83,7 @@ # Use cmd_get_dg_system_record_report() to get the system record in an excel # This function gets an address to locate the report there (i.e. /home/fw/projects/) # It creates a folder called DG_NV_Records in the destination that is called - # If no address is provided, the default location is one folder above the leahi-dialin folder wherever it is installed + # If no address is provided, the default location is one folder above the leahi_dialin folder wherever it is installed # in your computer. #hd.usage_record.cmd_get_hd_usage_info_record() @@ -98,7 +98,7 @@ # Use cmd_get_dg_system_record_report() to get the system record in an excel # This function gets an address to locate the report there (i.e. /home/fw/projects/) # It creates a folder called DG_NV_Records in the destination that is called - # If no address is provided, the default location is one folder above the leahi-dialin folder wherever it is installed + # If no address is provided, the default location is one folder above the leahi_dialin folder wherever it is installed # in your computer. #hd.service_record.cmd_get_hd_service_record() Index: tests/peter/test_dg_records.py =================================================================== diff -u -r07db7b5f01ad17d60c190e21574e1ed0552535ff -r2138d06d100fdcf23f2e9069f35ee2fdee62008f --- tests/peter/test_dg_records.py (.../test_dg_records.py) (revision 07db7b5f01ad17d60c190e21574e1ed0552535ff) +++ tests/peter/test_dg_records.py (.../test_dg_records.py) (revision 2138d06d100fdcf23f2e9069f35ee2fdee62008f) @@ -266,11 +266,11 @@ #print(dg.calibration_record.dg_calibration_record) #record_old_formatted = pprint.pformat(dg.calibration_record.dg_calibration_record, indent=4) - #with open("/home/fw/projects/leahi-dialin/tests/peter/dg_cal_record.log", 'w') as f: + #with open("/home/fw/projects/leahi_dialin/tests/peter/dg_cal_record.log", 'w') as f: # f.write(record_old_formatted) - #subprocess.call("meld /home/fw/projects/leahi-dialin/tests/peter/dialin_test_record_old.log" - # " /home/fw/projects/leahi-dialin/tests/peter/dialin_test_record_new.log", shell=True) + #subprocess.call("meld /home/fw/projects/leahi_dialin/tests/peter/dialin_test_record_old.log" + # " /home/fw/projects/leahi_dialin/tests/peter/dialin_test_record_new.log", shell=True) def test_dg_reset_record(): Index: tests/peter/test_gen_requirements.py =================================================================== diff -u -r07db7b5f01ad17d60c190e21574e1ed0552535ff -r2138d06d100fdcf23f2e9069f35ee2fdee62008f --- tests/peter/test_gen_requirements.py (.../test_gen_requirements.py) (revision 07db7b5f01ad17d60c190e21574e1ed0552535ff) +++ tests/peter/test_gen_requirements.py (.../test_gen_requirements.py) (revision 2138d06d100fdcf23f2e9069f35ee2fdee62008f) @@ -50,7 +50,7 @@ def gen_cmd_requirements(module) -> dict: """ - Generates requirements from leahi-dialin commands + Generates requirements from leahi_dialin commands @param module: the module to generate command requirements for @return: """ Index: tests/peter/test_hd_records.py =================================================================== diff -u -r07db7b5f01ad17d60c190e21574e1ed0552535ff -r2138d06d100fdcf23f2e9069f35ee2fdee62008f --- tests/peter/test_hd_records.py (.../test_hd_records.py) (revision 07db7b5f01ad17d60c190e21574e1ed0552535ff) +++ tests/peter/test_hd_records.py (.../test_hd_records.py) (revision 2138d06d100fdcf23f2e9069f35ee2fdee62008f) @@ -52,7 +52,7 @@ """ # store the old record after reading it from fw record_old_formatted = pprint.pformat(hd.calibration_record.hd_calibration_record, indent=4) - with open("/home/fw/projects/leahi-dialin/tests/peter/hd_cal_record.log", 'w') as f: + with open("/home/fw/projects/leahi_dialin/tests/peter/hd_cal_record.log", 'w') as f: f.write(record_old_formatted) """ @@ -120,7 +120,7 @@ sleep(0.2) record_old_formatted = pprint.pformat(hd.system_record.hd_system_record, indent=4) - with open("/home/fw/projects/leahi-dialin/tests/peter/hd_sys_record_old.log", 'w') as f: + with open("/home/fw/projects/leahi_dialin/tests/peter/hd_sys_record_old.log", 'w') as f: f.write(record_old_formatted) hd.system_record.hd_system_record['system_record']['top_level_pn'][1] = 'ASD-S123' Index: tests/peter/test_logging.py =================================================================== diff -u -r07db7b5f01ad17d60c190e21574e1ed0552535ff -r2138d06d100fdcf23f2e9069f35ee2fdee62008f --- tests/peter/test_logging.py (.../test_logging.py) (revision 07db7b5f01ad17d60c190e21574e1ed0552535ff) +++ tests/peter/test_logging.py (.../test_logging.py) (revision 2138d06d100fdcf23f2e9069f35ee2fdee62008f) @@ -59,8 +59,8 @@ hd.vvlogger.debug("VVLOGGER: TEST 1 (BEFORE DIALIN logger.debug() calls)") hd.test_debug() hd.vvlogger.debug("VVLOGGER: TEST 2 (AFTER DIALIN logger.debug() calls)") - # observe that no leahi-dialin logger.debug() messages appear in vvlogger.log - # and no leahi-dialin logger.debug() messages appear in the pycharm console (same behavior when running from terminal). + # observe that no leahi_dialin logger.debug() messages appear in vvlogger.log + # and no leahi_dialin logger.debug() messages appear in the pycharm console (same behavior when running from terminal). if __name__ == '__main__': test_create_logger() Index: tests/test_flush.py =================================================================== diff -u -r07db7b5f01ad17d60c190e21574e1ed0552535ff -r2138d06d100fdcf23f2e9069f35ee2fdee62008f --- tests/test_flush.py (.../test_flush.py) (revision 07db7b5f01ad17d60c190e21574e1ed0552535ff) +++ tests/test_flush.py (.../test_flush.py) (revision 2138d06d100fdcf23f2e9069f35ee2fdee62008f) @@ -108,7 +108,7 @@ def run_flush_mode(): complete_counter = 1 - f = open("/home/fw/projects/leahi-dialin/tests/flush_mode.log", "w") + f = open("/home/fw/projects/leahi_dialin/tests/flush_mode.log", "w") dg.hd_proxy.cmd_start_stop_dg_flush() #dg.cmd_dg_software_reset_request() Index: tests/test_hd_dg_fans.py =================================================================== diff -u -r07db7b5f01ad17d60c190e21574e1ed0552535ff -r2138d06d100fdcf23f2e9069f35ee2fdee62008f --- tests/test_hd_dg_fans.py (.../test_hd_dg_fans.py) (revision 07db7b5f01ad17d60c190e21574e1ed0552535ff) +++ tests/test_hd_dg_fans.py (.../test_hd_dg_fans.py) (revision 2138d06d100fdcf23f2e9069f35ee2fdee62008f) @@ -16,7 +16,7 @@ from dialin import HD, DG from dialin.hd.temperatures import HDTemperaturesNames -#from leahi-dialin.hd.fans import FansNames +#from leahi_dialin.hd.fans import FansNames from dialin.hd.switches import HDSwitchesNames from time import sleep Index: tests/test_voltages.py =================================================================== diff -u -r07db7b5f01ad17d60c190e21574e1ed0552535ff -r2138d06d100fdcf23f2e9069f35ee2fdee62008f --- tests/test_voltages.py (.../test_voltages.py) (revision 07db7b5f01ad17d60c190e21574e1ed0552535ff) +++ tests/test_voltages.py (.../test_voltages.py) (revision 2138d06d100fdcf23f2e9069f35ee2fdee62008f) @@ -47,7 +47,7 @@ x = 0 # create log file - with open("/home/fw/projects/leahi-dialin/tests/v_test.log", "w") as f: + with open("/home/fw/projects/leahi_dialin/tests/v_test.log", "w") as f: # write column header labels to log file header = "HD1.2V, HD3.3V, HD5V Logic, HD5V Sensors, HD24V, HD24V Regen, HDFPGA RefV, HDPBA RefV, HDAlmCurrHg, HDAlmCurrLg, HDAlmBckpCurr," + \ "DG1V FPGA, DG1.2V, DG1.8V Proc, DG1.8V FPGA, DGVRef, DGRef1, DGRef2, DG3.3V, DG3.3V Sensors, DG5V Logic, DG5V Sensors, DG5V P/S Gate Drvr, DG24V, DG24V Htr, DG24V Trim\n" Index: tests/unit_tests/test_msg_ids.py =================================================================== diff -u -r07db7b5f01ad17d60c190e21574e1ed0552535ff -r2138d06d100fdcf23f2e9069f35ee2fdee62008f --- tests/unit_tests/test_msg_ids.py (.../test_msg_ids.py) (revision 07db7b5f01ad17d60c190e21574e1ed0552535ff) +++ tests/unit_tests/test_msg_ids.py (.../test_msg_ids.py) (revision 2138d06d100fdcf23f2e9069f35ee2fdee62008f) @@ -25,7 +25,7 @@ def test_msg_ids(self): os.chdir("../../") - for root, dirs, files in os.walk("leahi-dialin/"): + for root, dirs, files in os.walk("leahi_dialin/"): for file in files: if file.endswith(".py"): path = os.path.join(root, file) Index: tools/build_common_defs.py =================================================================== diff -u -r07db7b5f01ad17d60c190e21574e1ed0552535ff -r2138d06d100fdcf23f2e9069f35ee2fdee62008f --- tools/build_common_defs.py (.../build_common_defs.py) (revision 07db7b5f01ad17d60c190e21574e1ed0552535ff) +++ tools/build_common_defs.py (.../build_common_defs.py) (revision 2138d06d100fdcf23f2e9069f35ee2fdee62008f) @@ -163,7 +163,7 @@ build_common_defs(dst_alarms_txt="/tmp/AlarmIds.txt", dst_common="/tmp/common", common_branch=common_branch, - dst_python=os.path.join(os.path.abspath("../"), "leahi-dialin/common/alarm_defs.py"), + dst_python=os.path.join(os.path.abspath("../"), "leahi_dialin/common/alarm_defs.py"), author="Peter Lucia", prefix_match="ALARM_ID_", cpp_header="AlarmDefs.h", @@ -185,7 +185,7 @@ build_common_defs(dst_alarms_txt="/tmp/MsgDefs.txt", dst_common="/tmp/common", common_branch=common_branch, - dst_python=os.path.join(os.path.abspath("../"), "leahi-dialin/common/msg_ids.py"), + dst_python=os.path.join(os.path.abspath("../"), "leahi_dialin/common/msg_ids.py"), author="Peter Lucia", prefix_match="MSG_ID_", cpp_header="MsgDefs.h",