Index: dialin/common/msg_ids.py =================================================================== diff -u -rc395352cad31f029410029b2df987fbe21934843 -rf876b77eab64261c89b8928b038e18de8125742a --- dialin/common/msg_ids.py (.../msg_ids.py) (revision c395352cad31f029410029b2df987fbe21934843) +++ dialin/common/msg_ids.py (.../msg_ids.py) (revision f876b77eab64261c89b8928b038e18de8125742a) @@ -132,12 +132,14 @@ MSG_ID_UI_TREATMENT_LOG_DATA_REQUEST = 0X75 MSG_ID_HD_TREATMENT_LOG_DATA_RESPONSE = 0X76 MSG_ID_HD_POST_TREATMENT_STATE = 0X77 - MSG_ID___AVAILABLE_16 = 0X78 + MSG_ID_DG_START_STOP_CHEM_DISINFECT = 0X78 MSG_ID_DG_START_STOP_FLUSH = 0X79 MSG_ID_DG_FLUSH_DATA = 0X7A MSG_ID_HD_VOLTAGES_DATA = 0X7B MSG_ID_HD_ALARM_AUDIO_VOLUME_SET_RESPONSE = 0X7C MSG_ID_HD_ALARM_INFORMATION = 0X7D + MSG_ID_DG_VOLTAGES_DATA = 0X86 + MSG_ID_DG_CHEM_DISINFECT_DATA = 0X87 MSG_ID_DIALYSATE_FLOW_DATA = 0X8 MSG_ID_TESTER_LOGIN_REQUEST = 0X8000 MSG_ID_DIAL_OUT_FLOW_SET_PT_OVERRIDE = 0X8001 @@ -226,7 +228,6 @@ MSG_ID_HD_ALARM_AUDIO_CURRENT_LG_OVERRIDE = 0X8055 MSG_ID_HD_ALARM_BACKUP_AUDIO_CURRENT_OVERRIDE = 0X8056 MSG_ID_HD_VALVES_CURRENT_OVERRIDE = 0X8057 - MSG_ID_DG_VOLTAGES_DATA = 0X86 MSG_ID_PRESSURE_OCCLUSION_DATA = 0X9 MSG_ID_CAN_ERROR_COUNT = 0X999 MSG_ID_RTC_EPOCH = 0XA Index: dialin/dg/chemical_disinfect.py =================================================================== diff -u --- dialin/dg/chemical_disinfect.py (revision 0) +++ dialin/dg/chemical_disinfect.py (revision f876b77eab64261c89b8928b038e18de8125742a) @@ -0,0 +1,104 @@ + +import struct +from ..common.msg_defs import MsgIds, MsgFieldPositions +from ..protocols.CAN import (DenaliMessage, DenaliChannels) +from ..utils.base import _AbstractSubSystem, _publish, DialinEnum +from logging import Logger +from enum import unique + + +@unique +class ChemicalDisinfectStates(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_FLUSH_CIRCULATION = 4 + DG_CHEM_DISINFECT_STATE_FLUSH_R1_AND_R2 = 5 + DG_CHEM_DISINFECT_STATE_FLUSH_R2_AND_DRAIN_R1 = 6 + DG_CHEM_DISINFECT_STATE_FLUSH_DRAIN_R2 = 7 + DG_CHEM_DISINFECT_STATE_FLUSH_DRAIN_R1 = 8 + DG_CHEM_DISINFECT_STATE_FILL_WITH_WATER_AND_DISINFECTANT = 9 + DG_CHEM_DISINFECT_STATE_DISINFECT_R1_TO_R2 = 10 + DG_CHEM_DISINFECT_STATE_FILL_R2_WITH_DISINFECTANT = 11 + DG_CHEM_DISINFECT_STATE_DISINFECT_R2_TO_R1 = 12 + DG_CHEM_DISINFECT_STATE_DISINFECTANT_DRAIN_R1 = 13 + DG_CHEM_DISINFECT_STATE_DISINFECTANT_DRAIN_R2 = 14 + DG_CHEM_DISINFECT_STATE_RINSE_R1_TO_R2 = 15 + DG_CHEM_DISINFECT_STATE_RINSE_R2_TO_R1_AND_DRAIN_R1 = 16 + DG_CHEM_DISINFECT_STATE_RINSE_R1_TO_R2_AND_DRAIN_R2 = 17 + DG_CHEM_DISINFECT_STATE_RINSE_CIRCULATION = 18 + DG_CHEM_DISINFECT_STATE_CANCEL_BASIC_PATH = 19 + DG_CHEM_DISINFECT_STATE_COMPLETE = 20 + + +@unique +class CancellationModes(DialinEnum): + + CANCELLATION_MODE_NONE = 0 + CANCELLATION_MODE_BASIC = 1 + CANCELLATION_MODE_HOT = 2 + CANCELLATION_MODE_COLD = 3 + + +class ChemicalDisinfect(_AbstractSubSystem): + """ + \class ChemicalDisinfect + + \brief Chemical disinfect class with APIs to set the timing of each of the stages. + """ + + def __init__(self, can_interface, logger: Logger): + + super().__init__() + + self.can_interface = can_interface + self.logger = logger + + self.chemical_disinfect_state = 0 + self.overall_elapsed_time = 0 + self.state_elapsed_time = 0 + self.disinfect_elapsed_time = 0 + self.cancellation_mode = 0 + self.r1_level = 0 + self.r2_level = 0 + + if self.can_interface is not None: + channel_id = DenaliChannels.dg_sync_broadcast_ch_id + msg_id = MsgIds.MSG_ID_DG_CHEM_DISINFECT_DATA.value + self.can_interface.register_receiving_publication_function(channel_id, msg_id, + self._handler_chemical_disinfect_sync) + + @_publish(["chemical_disinfect_state", "overall_elapsed_time", "state_elapsed_time", "cancellation_mode", "r1_level", + "r2_level"]) + def _handler_chemical_disinfect_sync(self, message): + """ + Handles published chemical disinfect message + + @param message: published chemical disinfect data message + @returns none + """ + state = struct.unpack('i', bytearray( + message['message'][MsgFieldPositions.START_POS_FIELD_1:MsgFieldPositions.END_POS_FIELD_1]))[0] + elapsed_time = struct.unpack('i', bytearray( + message['message'][MsgFieldPositions.START_POS_FIELD_2:MsgFieldPositions.END_POS_FIELD_2]))[0] + state_elapsed_time = struct.unpack('i', bytearray( + message['message'][MsgFieldPositions.START_POS_FIELD_3:MsgFieldPositions.END_POS_FIELD_3]))[0] + disinfect_elapsed_time = struct.unpack('i', bytearray( + message['message'][MsgFieldPositions.START_POS_FIELD_4:MsgFieldPositions.END_POS_FIELD_4]))[0] + cancellation_mode = struct.unpack('i', bytearray( + message['message'][MsgFieldPositions.START_POS_FIELD_5:MsgFieldPositions.END_POS_FIELD_5]))[0] + r1 = struct.unpack('f', bytearray( + message['message'][MsgFieldPositions.START_POS_FIELD_6:MsgFieldPositions.END_POS_FIELD_6]))[0] + r2 = struct.unpack('f', bytearray( + message['message'][MsgFieldPositions.START_POS_FIELD_7:MsgFieldPositions.END_POS_FIELD_7]))[0] + + self.chemical_disinfect_state = state + self.overall_elapsed_time = int(elapsed_time / 1000) + self.state_elapsed_time = int(state_elapsed_time / 1000) + self.disinfect_elapsed_time = int(disinfect_elapsed_time / 1000) + self.cancellation_mode = cancellation_mode + + self.r1_level = r1 + self.r2_level = r2 \ No newline at end of file Index: dialin/dg/dialysate_generator.py =================================================================== diff -u -r2700dbd3c0ced061a3d8141eb00e9e3a7a3e2817 -rf876b77eab64261c89b8928b038e18de8125742a --- dialin/dg/dialysate_generator.py (.../dialysate_generator.py) (revision 2700dbd3c0ced061a3d8141eb00e9e3a7a3e2817) +++ dialin/dg/dialysate_generator.py (.../dialysate_generator.py) (revision f876b77eab64261c89b8928b038e18de8125742a) @@ -44,6 +44,7 @@ from ..utils.base import _AbstractSubSystem, _publish, _LogManager from ..common.msg_defs import MsgIds, MsgFieldPositions from .flush import FlushMode +from .chemical_disinfect import ChemicalDisinfect class DG(_AbstractSubSystem): @@ -167,6 +168,7 @@ self.scheduled_runs_record = DGScheduledRunsNVRecord(self.can_interface, self.logger) self.valves = DGValves(self.can_interface, self.logger) self.flush = FlushMode(self.can_interface, self.logger) + self.chemical_disinfect = ChemicalDisinfect(self.can_interface, self.logger) def get_version(self): """ Index: dialin/dg/hd_proxy.py =================================================================== diff -u -r7d528c2109af39ed65e9dd3d61162de89ad22d96 -rf876b77eab64261c89b8928b038e18de8125742a --- dialin/dg/hd_proxy.py (.../hd_proxy.py) (revision 7d528c2109af39ed65e9dd3d61162de89ad22d96) +++ dialin/dg/hd_proxy.py (.../hd_proxy.py) (revision f876b77eab64261c89b8928b038e18de8125742a) @@ -296,3 +296,34 @@ else: self.logger.debug("Timeout!!!!") return False + + def cmd_start_stop_dg_chemical_disinfect(self, start:bool=True) -> int: + """ + Constructs and sends the start/stop DG chemical disinfect command + + @param start: (bool) True = start chemical disinfect, False = stop chemical disinfect. + @return: non-zero integer if successful, False otherwise + """ + # 1 is to start + if start: + cmd = 1 + str = "Starting" + else: + cmd = 0 + str = "Stopping" + payload = integer_to_bytearray(cmd) + message = DenaliMessage.build_message(channel_id=DenaliChannels.dialin_to_dg_ch_id, + message_id=MsgIds.MSG_ID_DG_START_STOP_CHEM_DISINFECT.value, + payload=payload) + + self.logger.debug(str + " DG chemical disinfect") + + 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 \ No newline at end of file Index: dialin/dg/heat_disinfect.py =================================================================== diff -u -r01db18a18769df2048346f868412fc8a12631fc0 -rf876b77eab64261c89b8928b038e18de8125742a --- dialin/dg/heat_disinfect.py (.../heat_disinfect.py) (revision 01db18a18769df2048346f868412fc8a12631fc0) +++ dialin/dg/heat_disinfect.py (.../heat_disinfect.py) (revision f876b77eab64261c89b8928b038e18de8125742a) @@ -1,6 +1,5 @@ import struct -from ..utils.conversions import integer_to_bytearray from ..common.msg_defs import MsgIds, MsgFieldPositions from ..protocols.CAN import (DenaliMessage, DenaliChannels) from ..utils.base import _AbstractSubSystem, _publish, DialinEnum @@ -48,7 +47,7 @@ """ \class HeatDisinfect - \brief Heat disinfection class with APIs to set the timing of each of the stages. + \brief Heat disinfect class with APIs to set the timing of each of the stages. """ def __init__(self, can_interface, logger: Logger): @@ -76,9 +75,9 @@ "r2_level"]) def _handler_heat_disinfect_sync(self, message): """ - Handles published heat disinfection message + Handles published heat disinfect message - @param message: published heat disinfection data message + @param message: published heat disinfect data message @returns none """ state = struct.unpack('i', bytearray( Index: tests/dg_heat_and_chemical_disinfect_test.py =================================================================== diff -u --- tests/dg_heat_and_chemical_disinfect_test.py (revision 0) +++ tests/dg_heat_and_chemical_disinfect_test.py (revision f876b77eab64261c89b8928b038e18de8125742a) @@ -0,0 +1,228 @@ + + +import sys +sys.path.append("..") +from dialin.dg.dialysate_generator import DG +from dialin.dg.heat_disinfect import HeatDisinfectStates, CancellationModes +from dialin.dg.chemical_disinfect import ChemicalDisinfectStates, CancellationModes +from dialin.dg.drain_pump import DrainPumpStates +from dialin.dg.thermistors import ThermistorsNames +from dialin.dg.temperature_sensors import TemperatureSensorsNames +from time import sleep + + +def get_chemical_disinfect_mode_info(): + info = ('State, {}, Overall_elapsed_time, {}, State_elapsed_time, {}, Disinfect_elapsed_time, {}, ' + 'Cancellation_mode, {}, R1_level, {:5.3f}, R1_drift, {:5.3f}, R2_level, {:5.3f}, R2_drift, {:5.3f} ' + .format(ChemicalDisinfectStates(dg.chemical_disinfect.chemical_disinfect_state).name, + dg.chemical_disinfect.overall_elapsed_time, dg.chemical_disinfect.state_elapsed_time, + dg.chemical_disinfect.disinfect_elapsed_time, + CancellationModes(dg.chemical_disinfect.cancellation_mode).name, dg.chemical_disinfect.r1_level, + (dg.chemical_disinfect.r1_level - dg.load_cells.load_cell_A1), dg.chemical_disinfect.r2_level, + (dg.chemical_disinfect.r2_level - dg.load_cells.load_cell_B1))) + return info + + +def get_concentrate_pumps_info(): + pass + # TODO add concentrate pumps and the conductivity sensors information + + +def get_heat_disinfect_mode_info(): + + info = ('State, {}, Overall_elapsed_time, {}, State_elapsed_time, {}, Disinfect_elapsed_time, {}, ' + 'Cancellation_mode, {}, R1_level, {:5.3f}, R1_drift, {:5.3f}, R2_level, {:5.3f}, R2_drift, {:5.3f} ' + .format(HeatDisinfectStates(dg.heat_disinfect.heat_disinfect_state).name, + dg.heat_disinfect.overall_elapsed_time, dg.heat_disinfect.state_elapsed_time, + dg.heat_disinfect.disinfect_elapsed_time, + CancellationModes(dg.heat_disinfect.cancellation_mode).name, dg.heat_disinfect.r1_level, + (dg.heat_disinfect.r1_level - dg.load_cells.load_cell_A1), dg.heat_disinfect.r2_level, + (dg.heat_disinfect.r2_level - dg.load_cells.load_cell_B1))) + return info + + +def get_dg_valves_states(): + + info = ('VPi, {}, VSP, {}, VPd, {}, VBf, {}, VPo, {}, VDr, {}, VRc, {}, VRo, {}, VRd, {}, VRi, {}, VRf, {}, ' + .format(dg.valves.valve_states_enum[dg.valves.VALVE_PRESSURE_INLET], + dg.valves.valve_states_enum[dg.valves.VALVE_SAMPLING_PORT], + dg.valves.valve_states_enum[dg.valves.VALVE_PRODUCTION_DRAIN], + dg.valves.valve_states_enum[dg.valves.VALVE_BYPASS_FILTER], + dg.valves.valve_states_enum[dg.valves.VALVE_PRESSURE_OUTLET], + dg.valves.valve_states_enum[dg.valves.VALVE_DRAIN], + dg.valves.valve_states_enum[dg.valves.VALVE_RECIRCULATE], + dg.valves.valve_states_enum[dg.valves.VALVE_RESERVOIR_OUTLET], + dg.valves.valve_states_enum[dg.valves.VALVE_RESERVOIR_DRAIN], + dg.valves.valve_states_enum[dg.valves.VALVE_RESERVOIR_INLET], + dg.valves.valve_states_enum[dg.valves.VALVE_RESERVOIR_FILL])) + return info + + +def get_drain_states_info(): + + info = ('Drain, {}, DAC, {}, RPM, {}, PRd, {:5.3f}, PDr, {:5.3f}, '. + format(DrainPumpStates(dg.drain_pump.drain_pump_state).name, + dg.drain_pump.dac_value, dg.drain_pump.current_drain_pump_rpm, + dg.pressures.drain_pump_inlet_pressure, + dg.pressures.drain_pump_outlet_pressure)) + return info + + +def get_load_cells_info(): + + info = ('A1, {:5.3f}, A2, {:5.3f}, B1, {:5.3f}, B2, {:5.3f}, '. + format(dg.load_cells.load_cell_A1, dg.load_cells.load_cell_A2, dg.load_cells.load_cell_B1, + dg.load_cells.load_cell_B2)) + return info + + +def get_ro_info(): + + info = ('RO, {}, PPi, {:5.3f}, PPo, {:5.3f}, PWM, {:5.3f}, Flow, {:5.3f}, Tgt_flow, {:5.3f}, ' + .format(dg.ro_pump.ro_pump_state, dg.pressures.ro_pump_inlet_pressure, + dg.pressures.ro_pump_outlet_pressure, dg.ro_pump.pwm_duty_cycle_pct, + dg.ro_pump.measured_flow_rate_lpm, dg.ro_pump.target_flow_lpm)) + return info + + +def get_heaters_info(): + + info = ('Pri_main_DC, {:5.3f}, Pri_small_DC, {:5.3f}, Pri_int_temp, {:5.3f}, Prim_CJ_temp, {:5.3f}, ' + 'Prim_TC_temp, {:5.3f}, Trimmer_DC, {:5.3f}, Trim_int_temp, {:5.3f}, Trim_CJ_temp, {:5.3f}, ' + 'Trim_TC_temp, {:5.3f}, Prim_TC_raw, {:5.3f}, Prim_CJ_raw, {:5.3f}, Trimmer_TC_raw, {:5.3f}, ' + 'Trimmer_CJ_raw, {:5.3f}, Primary_target_temp, {:5.3f}, Trimmer_target_temp, {:5.3f}, '. + format(dg.heaters.main_primary_heater_duty_cycle, dg.heaters.small_primary_heater_duty_cycle, + dg.temperature_sensors.temperature_sensors[TemperatureSensorsNames.PRIMARY_HEATER_INTERNAL.name], + dg.temperature_sensors.temperature_sensors[TemperatureSensorsNames.PRIMARY_HEATER_COLD_JUNCTION.name], + dg.temperature_sensors.temperature_sensors[TemperatureSensorsNames.PRIMARY_HEATER_THERMOCOUPLE.name], + dg.heaters.trimmer_heater_duty_cycle, + dg.temperature_sensors.temperature_sensors[TemperatureSensorsNames.TRIMMER_HEATER_INTERNAL.name], + dg.temperature_sensors.temperature_sensors[TemperatureSensorsNames.TRIMMER_HEATER_COLD_JUNCTION.name], + dg.temperature_sensors.temperature_sensors[TemperatureSensorsNames.TRIMMER_HEATER_THERMOCOUPLE.name], + dg.temperature_sensors.primary_raw_thermo_couple, dg.temperature_sensors.primary_raw_cold_junc, + dg.temperature_sensors.trimmer_raw_thermo_couple, dg.temperature_sensors.trimmer_raw_cold_junc, + dg.heaters.primary_heaters_target_temperature, dg.heaters.trimmer_heater_target_temperature)) + return info + + +def get_temperature_sensors_info(): + + info = ('TPi, {:5.3f}, TPo, {:5.3f}, TD1, {:5.3f}, TD2, {:5.3f}, TRo, {:5.3f}, TDi, {:5.3f}, ' + .format(dg.temperature_sensors.temperature_sensors[TemperatureSensorsNames.INLET_PRIMARY_HEATER.name], + dg.temperature_sensors.temperature_sensors[TemperatureSensorsNames.OUTLET_PRIMARY_HEATER.name], + dg.temperature_sensors.temperature_sensors[TemperatureSensorsNames.CONDUCTIVITY_SENSOR_1.name], + dg.temperature_sensors.temperature_sensors[TemperatureSensorsNames.CONDUCTIVITY_SENSOR_2.name], + dg.temperature_sensors.temperature_sensors[TemperatureSensorsNames.OUTLET_DIALYSATE_REDUNDANT.name], + dg.temperature_sensors.temperature_sensors[TemperatureSensorsNames.INLET_DIALYSATE.name])) + return info + + +def get_fans_info(): + + info = ('Target_fans_DC, {:5.3f}, Inlet1_RPM, {:5.3f}, Outlet1_RPM, {:5.3f}, ' + 'Inlet2_RPM, {:5.3f}, Outlet2_RPM, {:5.3f}, Inlet3_RPM, {:5.3f}, Outlet3_RPM, {:5.3f}, Board_temp, {:5.3f},' + ' FPGA_temp, {:5.3f}, Load_cell_A1_B1, {:5.3f}, Load_cell_A2_B2, {:5.3f}, ' + .format(dg.fans.target_duty_cycle, dg.fans.inlet_1_rpm, dg.fans.inlet_2_rpm, dg.fans.inlet_2_rpm, + dg.fans.outlet_2_rpm, dg.fans.inlet_3_rpm, dg.fans.outlet_3_rpm, + dg.thermistors.thermistors[ThermistorsNames.THERMISTOR_ONBOARD_NTC.name], + dg.temperature_sensors.temperature_sensors[TemperatureSensorsNames.FPGA_BOARD_SENSOR.name], + dg.temperature_sensors.temperature_sensors[TemperatureSensorsNames.LOAD_CELL_A1_B1.name], + dg.temperature_sensors.temperature_sensors[TemperatureSensorsNames.LOAD_CELL_A2_B2.name])) + return info + + +def run_heat_disinfect(): + + complete_counter = 1 + f = open("/home/fw/projects/dialin/tests/Heat_disinfect.log", "w") + dg.hd_proxy.cmd_start_stop_heat_disinfect() + + try: + while True: + + disinfect = get_heat_disinfect_mode_info() + drain = get_drain_states_info() + load_cell = get_load_cells_info() + valves = get_dg_valves_states() + ro = get_ro_info() + temp = get_temperature_sensors_info() + heaters = get_heaters_info() + fans = get_fans_info() + + var = disinfect + load_cell + drain + ro + temp + heaters + fans + valves + '\r' + + print(var) + f.write(var) + sleep(1) + + # If the mode came back to standby or standby solo + if dg.dg_operation_mode == 3 or dg.dg_operation_mode == 4: + # If it is the first call, stop heat disinfect + if complete_counter == 1: + dg.hd_proxy.cmd_start_stop_heat_disinfect(start=False) + # Write a few more complete states to make sure the complete state items are recorded + elif complete_counter == 3: + #pass + f.close() + break + + complete_counter += 1 + + except KeyboardInterrupt: + dg.hd_proxy.cmd_start_stop_heat_disinfect(start=False) + f.close() + + +def run_chemical_disinfect(): + + complete_counter = 1 + f = open("/home/fw/projects/dialin/tests/chemical_disinfect.log", "w") + dg.hd_proxy.cmd_start_stop_dg_chemical_disinfect() + + try: + while True: + + disinfect = get_chemical_disinfect_mode_info() + drain = get_drain_states_info() + load_cell = get_load_cells_info() + valves = get_dg_valves_states() + ro = get_ro_info() + temp = get_temperature_sensors_info() + heaters = get_heaters_info() + fans = get_fans_info() + + var = disinfect + load_cell + drain + ro + temp + heaters + fans + valves + '\r' + + print(var) + f.write(var) + sleep(1) + + # If the mode came back to standby or standby solo + if dg.dg_operation_mode == 3 or dg.dg_operation_mode == 4: + # If it is the first call, stop heat disinfect + if complete_counter == 1: + dg.hd_proxy.cmd_start_stop_heat_disinfect(start=False) + # Write a few more complete states to make sure the complete state items are recorded + elif complete_counter == 3: + #pass + f.close() + break + + complete_counter += 1 + + except KeyboardInterrupt: + dg.hd_proxy.cmd_start_stop_heat_disinfect(start=False) + f.close() + + +if __name__ == "__main__": + + dg = DG(log_level='DEBUG') + dg.cmd_log_in_to_dg() + sleep(1) + + run_heat_disinfect() + + #run_chemical_disinfect() + + Fisheye: Tag f876b77eab64261c89b8928b038e18de8125742a refers to a dead (removed) revision in file `tests/dg_heat_disinfect_test.py'. Fisheye: No comparison available. Pass `N' to diff?