class FLX05F(BaseValve): """Hydro Valve. Model: FLX-05F""" #region Attributes __logger = None """Logger """ __move_timer = None """Move timer. """ __calibration_state = None """Calibration state machine. """ __output_cw = verbal_const.OFF """Valve output CW GPIO. """ __output_ccw = verbal_const.OFF """Valve output CCW GPIO. """ __limit_cw = verbal_const.OFF """Limit switch for CW direction. """ __limit_ccw = verbal_const.OFF """Limit switch for CCW direction. """ __t0 = 0 """T0 moment. """ __t1 = 0 """T1 moment. """ __dt = 0 """Delta time consumed for one full open and close cycle. """ __limit_timer = None """Limit timer. """ __close_on_shutdown = True """Close on shutdown flag. """ #endregion #region Constructor / Destructor def __init__(self, **config): """Constructor """ super().__init__(config) self._vendor = "Flowx" self._model = "FLX-05F" # Create logger. self.__logger = get_logger(__name__) self.__move_timer = Timer() # 13 seconds absolute time to react the valve. # Number is measured by emperic way. self.__limit_timer = Timer(13) self.__calibration_state = StateMachine(CalibrationState.NONE) if "output_cw" in config: self.__output_cw = config["output_cw"] if "output_ccw" in config: self.__output_ccw = config["output_ccw"] if "limit_cw" in config: self.__limit_cw = config["limit_cw"] if "limit_ccw" in config: self.__limit_ccw = config["limit_ccw"] if "close_on_shutdown" in config: self.__close_on_shutdown = config["close_on_shutdown"] def __del__(self): """Destructor """ super().__del__() if self.__logger is not None: del self.__logger #endregion #region Properties @property def is_calibrating(self): """Return is it in calibration state. Returns: bool: True if calibrating, else false. """ return self._state.is_state(ValveState.Calibrate) #endregion #region Private Methods def __to_time(self, position): """Convert position in to time. Args: position (float): Position in %. Returns: float: Time in seconds. """ return position * (self.__dt / 100.0) #endregion #region Private Messatages (PLC I/O) def __stop(self): """Stop the valve motor. """ if self._controller.is_valid_gpio(self.__output_cw): self._controller.digital_write(self.__output_cw, 0) if self._controller.is_valid_gpio(self.__output_ccw): self._controller.digital_write(self.__output_ccw, 0) def __close_valve(self): """Turn to CW direction. """ if self._controller.is_valid_gpio(self.__output_cw): self._controller.digital_write(self.__output_cw, 1) def __open_valve(self): """Turn to CCW direction. """ if self._controller.is_valid_gpio(self.__output_ccw): self._controller.digital_write(self.__output_ccw, 1) def __get_open_limit(self): state = False if self._controller.is_valid_gpio(self.__limit_ccw): state = self._controller.digital_read(self.__limit_ccw) else: state = True return state def __get_close_limit(self): state = False if self._controller.is_valid_gpio(self.__limit_cw): state = self._controller.digital_read(self.__limit_cw) else: state = True return state #endregion #region Public Methods def init(self): """Initialize the device. """ self.target_position = 0 while self.current_position != self.target_position: self.update() self.__logger.debug("Starting up the: {}".format(self.name)) def shutdown(self): """Shutdown the valve. """ if self.__close_on_shutdown: self.__close_valve() while self.__get_close_limit() == False: self.update() self.__stop() self.__logger.debug("Shutdown the: {}".format(self.name)) def update(self): """Update the valve state. """ if self._state.is_state(ValveState.Prepare): delta_pos = self.target_position - self.current_position if delta_pos == 0: self.__stop() self._state.set_state(ValveState.Wait) return time_to_move = self.__to_time(abs(delta_pos)) self.__logger.debug("Time: {}".format(time_to_move)) self.__move_timer.expiration_time = time_to_move self.__move_timer.update_last_time() if delta_pos > 0: self.__open_valve() elif delta_pos < 0: self.__close_valve() self._state.set_state(ValveState.Execute) elif self._state.is_state(ValveState.Execute): self.__move_timer.update() if self.__move_timer.expired: self.__move_timer.clear() self.__stop() self._current_position = self.target_position self._state.set_state(ValveState.Wait) cw_limit_state = False # self.__get_close_limit() ccw_limit_state = False # self.__get_open_limit() if cw_limit_state or ccw_limit_state: self.__stop() GlobalErrorHandler.log_hardware_limit( self.__logger, "{} has raised end position.".format(self.name)) self._current_position = self.target_position self._state.set_state(ValveState.Wait) elif self._state.is_state(ValveState.Calibrate): # Wait to start. if self.__calibration_state.is_state(CalibrationState.NONE): self.__calibration_state.set_state(CalibrationState.OpenValve) self.__stop() # Open the valve. if self.__calibration_state.is_state(CalibrationState.OpenValve): self.__stop() self.__open_valve() self.__calibration_state.set_state(CalibrationState.EnsureOpen) self.__limit_timer.update_last_time() # Wait until it si open at 100%. if self.__calibration_state.is_state(CalibrationState.EnsureOpen): # Get CCW limit switch state. ccw_limit_state = self.__get_open_limit() if ccw_limit_state: self.__t0 = time.time() self.__calibration_state.set_state( CalibrationState.CloseValve) # Prevent with timer, # if the valve is not reacting properly. self.__limit_timer.update() if self.__limit_timer.expired: self.__limit_timer.clear() self.__calibration_state.set_state(CalibrationState.Error) # Close the valve. if self.__calibration_state.is_state(CalibrationState.CloseValve): self.__stop() self.__close_valve() self.__calibration_state.set_state( CalibrationState.EnsureClose) self.__limit_timer.update_last_time() # Wait until it si open at 100%. if self.__calibration_state.is_state(CalibrationState.EnsureClose): # Get CW limit switch state. cw_limit_state = self.__get_close_limit() if cw_limit_state: self.__t1 = time.time() self.__calibration_state.set_state( CalibrationState.YouDoTheMath) # Prevent with timer, # if the valve is not reacting properly. self.__limit_timer.update() if self.__limit_timer.expired: self.__limit_timer.clear() self.__calibration_state.set_state(CalibrationState.Error) # Make calculations. if self.__calibration_state.is_state( CalibrationState.YouDoTheMath): self.__stop() self.__dt = self.__t1 - self.__t0 self._state.set_state(ValveState.Wait) # Close the valve. if self.__calibration_state.is_state(CalibrationState.Error): GlobalErrorHandler.log_hardware_malfunction( self.__logger, "The valve {} can not calibrated.".format(self.name)) self._state.set_state(ValveState.Wait) # - Close the valve. # - Ensure that the valve is closed 0deg. # - Record the time in T0. # - Open the valve. # - Ensure the the valve is opened 90deg. # - Record the time in T1. # - Store (T0 - T1) in dT # - Use dT and end position contacts to ensure that the valve is closed and opened. def calibrate(self): self._state.set_state(ValveState.Calibrate) def update_sync(self): """Update synchronious. """ while self.current_position != self.target_position: self.update()
class Zone(BasePlugin): """Air conditioner control logic. """ #region Attributes __logger = None """Logger""" __identifier = 0 """Number identifier.""" __temp_proc = None """Temperature processor.""" __air_temp_upper_dev = None """Air thermometer upper.""" __air_temp_cent_dev = None """Air thermometer central.""" __air_temp_lower_dev = None """Air thermometer lower.""" __queue_temperatures = None """Queue of the temperatures.""" __convector_dev = None """Convector device.""" __loop1_temp_dev = None """Loop 2 thermometer.""" __loop1_valve_dev = None """Loop 1 valve device.""" __loop1_flowmeter = None """Loop 1 flow metter.""" __loop1_leak_test = None """Loop 1 leak test.""" __loop2_temp_dev = None """Loop 2 thermometer.""" __loop2_valve_dev = None """Loop 2 valve device.""" __loop2_flowmeter = None """Loop 2 flow metter.""" __loop2_leak_teat = None """Loop 2 leak test.""" __update_timer = None """Main process update timer.""" __stop_timer = None """Stop timer.""" __thermal_mode = None """Thermal mode of the HVAC.""" __thermal_force_limit = 0 """Limit thermal force.""" __delta_time = 1 """Конфигурационен параметър, показващ за какво време назад се отчита изменението на температурата. Limits: (1 - 3)""" __adjust_temp = 0 """Зададено отклонение от температурата (задава се от дисплея до вратата или през мобилен телефон, вързан в локалната мрежа) Limits: (-2.5 : 2.5)""" __goal_building_temp = 0 """Целева температура на сградата. (подава се от централния сървър) Limits: (18-26)""" __delta_temp = 0 """Изменението на температурата от последните минути. Limits: (-3 : 3)""" __thermal_force = 0 """Каква топлинна сила трябва да приложим към системата (-100% означава максимално да охлаждаме, +100% - максимално да отопляваме)""" __stop_flag = False """HVAC Stop flag.""" __window_closed_input = verbal_const.OFF """Window closed sensor input.""" #endregion #region Constructor / Destructor def __init__(self, **config): """Constructor""" super().__init__(config) if "identifier" in config: self.__identifier = config["identifier"] def __del__(self): """Destructor""" if self.__loop1_temp_dev is not None: del self.__loop1_temp_dev if self.__loop1_valve_dev is not None: del self.__loop1_valve_dev if self.__loop1_flowmeter is not None: del self.__loop1_flowmeter if self.__loop2_temp_dev is not None: del self.__loop2_temp_dev if self.__loop2_valve_dev is not None: del self.__loop2_valve_dev if self.__loop2_flowmeter is not None: del self.__loop2_flowmeter if self.__thermal_mode is not None: del self.__thermal_mode if self.__update_timer is not None: del self.__update_timer if self.__stop_timer is not None: del self.__stop_timer if self.__loop1_leak_test is not None: del self.__loop1_leak_test if self.__loop2_leak_teat is not None: del self.__loop2_leak_teat if self.__queue_temperatures is not None: del self.__queue_temperatures super().__del__() if self.__logger is not None: del self.__logger #endregion #region Properties @property def temperature(self): """Measure temperature from the sensors. Средна температура на стаята. (измерва се от датчиците) Limits: (0-40) Returns ------- float Actual temperature in the room. """ # return the temperature. return self.__temp_proc.value #endregion #region Private Methods (Registers Parameters) def __update_rate_cb(self, register): # Check data type. if not (register.data_type == "float" or register.data_type == "int"): GlobalErrorHandler.log_bad_register_data_type(self.__logger, register) return # Check value. if register.value < 0: GlobalErrorHandler.log_bad_register_value(self.__logger, register) return if self.__update_timer.expiration_time != register.value: self.__update_timer.expiration_time = register.value def __delta_time_cb(self, register): # Check data type. if not (register.data_type == "float" or register.data_type == "int"): GlobalErrorHandler.log_bad_register_data_type(self.__logger, register) return # Check value. if register.value < 0: GlobalErrorHandler.log_bad_register_value(self.__logger, register) return if self.__delta_time != register.value: self.__delta_time = register.value def __thermal_mode_cb(self, register): # Check data type. if not register.data_type == "int": GlobalErrorHandler.log_bad_register_data_type(self.__logger, register) return mode = ThermalMode(register.value) self.__thermal_mode.set_state(mode) def __thermal_force_limit_cb(self, register): # Check data type. if not (register.data_type == "float" or register.data_type == "int"): GlobalErrorHandler.log_bad_register_data_type(self.__logger, register) return # Check value. if register.value < 0: GlobalErrorHandler.log_bad_register_value(self.__logger, register) return if register.value > 100: GlobalErrorHandler.log_bad_register_value(self.__logger, register) return self.__thermal_force_limit = register.value def __adjust_temp_cb(self, register): # Check data type. if not (register.data_type == "float" or register.data_type == "int"): GlobalErrorHandler.log_bad_register_data_type(self.__logger, register) return if self.__adjust_temp == register.value: return # @see https://experta.bg/L/S/122745/m/Fwntindd min_temp = 2.5 max_temp = -2.5 min_temp_reg = self._registers.by_name("{}.temp_{}.min".format(self.key, self.__identifier)) if min_temp_reg is not None: min_temp = min_temp_reg.value max_temp_reg = self._registers.by_name("{}.temp_{}.max".format(self.key, self.__identifier)) if max_temp_reg is not None: max_temp = max_temp_reg.value actual_temp = register.value if actual_temp < min_temp: actual_temp = min_temp if actual_temp > max_temp: actual_temp = max_temp self.__adjust_temp = actual_temp def __goal_building_temp_cb(self, register): # Check data type. if not (register.data_type == "float" or register.data_type == "int"): GlobalErrorHandler.log_bad_register_data_type(self.__logger, register) return # @see https://experta.bg/L/S/122745/m/Fwntindd min_temp = 18 max_temp = 26 actual_temp = register.value if actual_temp < min_temp: actual_temp = min_temp if actual_temp > max_temp: actual_temp = max_temp if self.__goal_building_temp != actual_temp: self.__goal_building_temp = actual_temp def __window_closed_input_cb(self, register): # Check data type. if not register.data_type == "str": GlobalErrorHandler.log_bad_register_data_type(self.__logger, register) return self.__window_closed_input = register.value #endregion #region Private Methods (Registers Thermometers) def __air_temp_cent_settings_cb(self, register): # Check data type. if not register.data_type == "json": GlobalErrorHandler.log_bad_register_value(self.__logger, register) return if register.value != {} and self.__air_temp_cent_dev is None: self.__air_temp_cent_dev = ThermometersFactory.create( name="Air temperature center", controller=self._controller, vendor=register.value['vendor'], model=register.value['model'], options=register.value['options']) if self.__air_temp_cent_dev is not None: self.__air_temp_cent_dev.init() self.__temp_proc.add(self.__air_temp_cent_dev) elif register.value == {} and self.__air_temp_cent_dev is not None: self.__temp_proc.add(self.__air_temp_cent_dev) self.__air_temp_cent_dev.shutdown() del self.__air_temp_cent_dev def __air_temp_lower_settings_cb(self, register): # Check data type. if not register.data_type == "json": GlobalErrorHandler.log_bad_register_value(self.__logger, register) return if register.value != {} and self.__air_temp_lower_dev is None: self.__air_temp_lower_dev = ThermometersFactory.create( controller=self._controller, name="Air temperature lower", vendor=register.value['vendor'], model=register.value['model'], options=register.value['options']) if self.__air_temp_lower_dev is not None: self.__air_temp_lower_dev.init() self.__temp_proc.add(self.__air_temp_lower_dev) elif register.value == {} and self.__air_temp_lower_dev is not None: self.__temp_proc.remove(self.__air_temp_lower_dev) self.__air_temp_lower_dev.shutdown() del self.__air_temp_lower_dev def __air_temp_upper_settings_cb(self, register): # Check data type. if not register.data_type == "json": GlobalErrorHandler.log_bad_register_value(self.__logger, register) return if register.value != {} and self.__air_temp_upper_dev is None: self.__air_temp_upper_dev = ThermometersFactory.create( controller=self._controller, name="Air temperature upper", vendor=register.value['vendor'], model=register.value['model'], options=register.value['options']) if self.__air_temp_upper_dev is not None: self.__air_temp_upper_dev.init() self.__temp_proc.add(self.__air_temp_upper_dev) elif register.value == {} and self.__air_temp_upper_dev is not None: self.__temp_proc.remove(self.__air_temp_upper_dev) self.__air_temp_upper_dev.shutdown() del self.__air_temp_upper_dev #endregion #region Private Methods (Registers Convector) def __convector_settings_cb(self, register): # Check data type. if not register.data_type == "json": GlobalErrorHandler.log_bad_register_data_type(self.__logger, register) return if register.value != {} and self.__convector_dev is None: self.__convector_dev = ConvectorsFactory.create( name="Convector {}".format(self.__identifier), controller=self._controller, vendor=register.value['vendor'], model=register.value['model'], options=register.value['options']) if self.__convector_dev is not None: self.__convector_dev.init() elif register.value == {} and self.__convector_dev is not None: self.__convector_dev.shutdown() del self.__convector_dev #endregion #region Private Methods (Registers Loop 1) def __loop1_flowmeter_settings_cb(self, register): # Check data type. if not register.data_type == "json": GlobalErrorHandler.log_bad_register_data_type(self.__logger, register) return if register.value != {} and self.__loop1_flowmeter is None: self.__loop1_flowmeter = FlowmetersFactory.create( name="Loop 1 flowmeter", controller=self._controller, vendor=register.value['vendor'], model=register.value['model'], options=register.value['options']) if self.__loop1_flowmeter is not None: self.__loop1_flowmeter.init() # 20 seconds is time for leak testing. self.__loop1_leak_test = LeakTest(self.__loop1_flowmeter, 20) self.__loop1_leak_test.on_result(self.__loop1_leaktest_result) elif register.value == {} and self.__loop1_flowmeter is not None: self.__loop1_flowmeter.shutdown() del self.__loop1_flowmeter del self.__loop1_leak_test def __loop1_temp_settings_cb(self, register): # Check data type. if not register.data_type == "json": GlobalErrorHandler.log_bad_register_data_type(self.__logger, register) return if register.value != {} and self.__loop1_temp_dev is None: self.__loop1_temp_dev = ThermometersFactory.create( controller=self._controller, name="Loop 1 temperature", vendor=register.value['vendor'], model=register.value['model'], options=register.value['options']) if self.__loop1_temp_dev is not None: self.__loop1_temp_dev.init() elif register.value == {} and self.__loop1_temp_dev is not None: self.__loop1_temp_dev.shutdown() del self.__loop1_temp_dev def __loop1_valve_settings_cb(self, register): # Check data type. if not register.data_type == "json": GlobalErrorHandler.log_bad_register_data_type(self.__logger, register) return if register.value != {} and self.__loop1_valve_dev is None: self.__loop1_valve_dev = ValveFactory.create( name="Loop 1 valve", controller=self._controller, vendor=register.value['vendor'], model=register.value['model'], options=register.value['options']) if self.__loop1_valve_dev is not None: self.__loop1_valve_dev.init() elif register.value == {} and self.__loop1_valve_dev is not None: self.__loop1_valve_dev.shutdown() del self.__loop1_valve_dev #endregion #region Private Methods (Registers Loop 2) def __loop2_flowmeter_settings_cb(self, register): # Check data type. if not register.data_type == "json": GlobalErrorHandler.log_bad_register_data_type(self.__logger, register) return if register.value != {} and self.__loop2_flowmeter is None: self.__loop2_flowmeter = FlowmetersFactory.create( name="Loop 2 flowmeter", controller=self._controller, vendor=register.value['vendor'], model=register.value['model'], options=register.value['options']) if self.__loop2_flowmeter is not None: self.__loop2_flowmeter.init() self.__loop2_leak_teat = LeakTest(self.__loop2_flowmeter, 20) self.__loop2_leak_teat.on_result(self.__loop2_leaktest_result) elif register.value == {} and self.__loop2_flowmeter is not None: self.__loop2_flowmeter.shutdown() del self.__loop2_flowmeter del self.__loop2_leak_teat def __loop2_temp_settings_cb(self, register): # Check data type. if not register.data_type == "json": GlobalErrorHandler.log_bad_register_data_type(self.__logger, register) return if register.value != {} and self.__loop2_temp_dev is None: self.__loop2_temp_dev = ThermometersFactory.create( controller=self._controller, name="Loop 2 temperature", vendor=register.value['vendor'], model=register.value['model'], options=register.value['options']) if self.__loop2_temp_dev is not None: self.__loop2_temp_dev.init() elif register.value == {} and self.__loop2_temp_dev is not None: self.__loop2_temp_dev.shutdown() del self.__loop2_temp_dev def __loop2_valve_settings_cb(self, register): # Check data type. if not register.data_type == "json": GlobalErrorHandler.log_bad_register_data_type(self.__logger, register) return if register.value != {} and self.__loop2_valve_dev is None: self.__loop2_valve_dev = ValveFactory.create( name="Loop 2 valve", controller=self._controller, vendor=register.value['vendor'], model=register.value['model'], options=register.value['options']) if self.__loop2_valve_dev is not None: self.__loop2_valve_dev.init() elif register.value == {} and self.__loop2_valve_dev is not None: self.__loop2_valve_dev.shutdown() del self.__loop2_valve_dev #endregion #region Private Methods (Registers envm) def __envm_energy_cb(self, register): # Check data type. if not ((register.data_type == "int") or (register.data_type == "float")): GlobalErrorHandler.log_bad_register_data_type(self.__logger, register) return # TODO: Get energy mode for the building. pass #endregion #region Private Methods (Ventilation Interface) def __set_ventilation(self, value): # Set the ventilation. self._registers.write("vent.hvac_setpoint_{}".format(self.__identifier), value) #endregion #region Private Methods (Registers Interface) def __init_registers(self): """Initialize the registers callbacks. """ # Air temperatures. air_temp_cent_settings = self._registers.by_name("{}.air_temp_cent_{}.settings".format(self.key, self.__identifier)) if air_temp_cent_settings is not None: air_temp_cent_settings.update_handlers = self.__air_temp_cent_settings_cb air_temp_cent_settings.update() air_temp_lower_settings = self._registers.by_name("{}.air_temp_lower_{}.settings".format(self.key, self.__identifier)) if air_temp_lower_settings is not None: air_temp_lower_settings.update_handlers = self.__air_temp_lower_settings_cb air_temp_lower_settings.update() air_temp_upper_settings = self._registers.by_name("{}.air_temp_upper_{}.settings".format(self.key, self.__identifier)) if air_temp_upper_settings is not None: air_temp_upper_settings.update_handlers = self.__air_temp_upper_settings_cb air_temp_upper_settings.update() # Convector convector_enable = self._registers.by_name("{}.convector_{}.settings".format(self.key, self.__identifier)) if convector_enable is not None: convector_enable.update_handlers = self.__convector_settings_cb convector_enable.update() # Loop 1 loop1_flowmeter = self._registers.by_name("{}.loop1_{}.flowmeter.settings".format(self.key, self.__identifier)) if loop1_flowmeter is not None: loop1_flowmeter.update_handlers = self.__loop1_flowmeter_settings_cb loop1_flowmeter.update() loop1_temp_settings = self._registers.by_name("{}.loop1_{}.temp.settings".format(self.key, self.__identifier)) if loop1_temp_settings is not None: loop1_temp_settings.update_handlers = self.__loop1_temp_settings_cb loop1_temp_settings.update() loop1_valve_enabled = self._registers.by_name("{}.loop1_{}.valve.settings".format(self.key, self.__identifier)) if loop1_valve_enabled is not None: loop1_valve_enabled.update_handlers = self.__loop1_valve_settings_cb loop1_valve_enabled.update() # Loop 2 loop2_flowmeter_settings = self._registers.by_name("{}.loop2_{}.flowmeter.settings".format(self.key, self.__identifier)) if loop2_flowmeter_settings is not None: loop2_flowmeter_settings.update_handlers = self.__loop2_flowmeter_settings_cb loop2_flowmeter_settings.update() loop2_temp_settings = self._registers.by_name("{}.loop2_{}.temp.settings".format(self.key, self.__identifier)) if loop2_temp_settings is not None: loop2_temp_settings.update_handlers = self.__loop2_temp_settings_cb loop2_temp_settings.update() loop2_valve_settings = self._registers.by_name("{}.loop2_{}.valve.settings".format(self.key, self.__identifier)) if loop2_valve_settings is not None: loop2_valve_settings.update_handlers = self.__loop2_valve_settings_cb loop2_valve_settings.update() # Create window closed sensor. window_closed_input = self._registers.by_name("{}.window_closed_{}.input".format("ac", self.__identifier)) if window_closed_input is not None: window_closed_input.update_handlers = self.__window_closed_input_cb window_closed_input.update() # Region parameters update_rate = self._registers.by_name("{}.update_rate_{}".format(self.key, self.__identifier)) if update_rate is not None: update_rate.update_handlers = self.__update_rate_cb update_rate.update() delta_time = self._registers.by_name("{}.delta_time_{}".format(self.key, self.__identifier)) if delta_time is not None: delta_time.update_handlers = self.__delta_time_cb delta_time.update() thermal_mode = self._registers.by_name("{}.thermal_mode_{}".format(self.key, self.__identifier)) if thermal_mode is not None: thermal_mode.update_handlers = self.__thermal_mode_cb thermal_mode.update() thermal_force_limit = self._registers.by_name("{}.thermal_force_limit_{}".format(self.key, self.__identifier)) if thermal_force_limit is not None: thermal_force_limit.update_handlers = self.__thermal_force_limit_cb thermal_force_limit.update() adjust_temp = self._registers.by_name("{}.adjust_temp_{}".format(self.key, self.__identifier)) if adjust_temp is not None: adjust_temp.update_handlers = self.__adjust_temp_cb adjust_temp.update() goal_building_temp = self._registers.by_name("{}.goal_building_temp".format(self.key)) if goal_building_temp is not None: goal_building_temp.update_handlers = self.__goal_building_temp_cb goal_building_temp.update() # Get the power mode of the building. envm_energy = self._registers.by_name("envm.energy") if envm_energy is not None: envm_energy.update_handlers = self.__envm_energy_cb envm_energy.update() def __update_thermometers_values(self): # 1. If thermometer is available, gets its value. air_temp_lower_value = 0 if self.__air_temp_lower_dev is not None: air_temp_lower_value = self.__air_temp_lower_dev.get_temp() # 1. If thermometer is available, gets its value. air_temp_cent_value = 0 if self.__air_temp_cent_dev is not None: air_temp_cent_value = self.__air_temp_cent_dev.get_temp() # 1. If thermometer is available, gets its value. air_temp_upper_value = 0 if self.__air_temp_upper_dev is not None: air_temp_upper_value = self.__air_temp_upper_dev.get_temp() # 1. If thermometer is available, gets its value. loop1_temp_value = 0 if self.__loop1_temp_dev is not None: loop1_temp_value = self.__loop1_temp_dev.get_temp() # 1. If thermometer is available, gets its value. loop2_temp_value = 0 if self.__loop2_temp_dev is not None: loop2_temp_value = self.__loop2_temp_dev.get_temp() # 2. If the folowing register is available then set ist value to the thermometers value. self._registers.write("{}.air_temp_lower_{}.value".format(self.key, self.__identifier), air_temp_lower_value) # 2. If the folowing register is available then set ist value to the thermometers value. self._registers.write("{}.air_temp_cent_{}.value".format(self.key, self.__identifier), air_temp_cent_value) # 2. If the folowing register is available then set ist value to the thermometers value. self._registers.write("{}.air_temp_upper_{}.value".format(self.key, self.__identifier), air_temp_upper_value) # 2. If the folowing register is available then set ist value to the thermometers value. self._registers.write("{}.loop1_{}.temp.value".format(self.key, self.__identifier), loop1_temp_value) # 2. If the folowing register is available then set ist value to the thermometers value. self._registers.write("{}.loop2_{}.temp.value".format(self.key, self.__identifier), loop2_temp_value) def __is_empty(self): value = False is_empty = self._registers.by_name("envm.is_empty") if is_empty is not None: value = is_empty.value return value def __get_down_limit_temp(self): # Request: Eml6419 value = 10 down_limit = self._registers.by_name("{}.loop1_{}.temp.down_limit".format(self.key, self.__identifier)) if down_limit is not None: down_limit_value = down_limit.value return value #endregion #region Private Methods (PLC) def __read_window_tamper(self): state = False if self._controller.is_valid_gpio(self.__window_closed_input): state = self._controller.digital_read(self.__window_closed_input) if self.__window_closed_input == verbal_const.OFF: state = True return state #endregion #region Private Methods (Leak tests) def __loop1_leaktest_result(self, leaked_liters): if leaked_liters > 0: self.__logger.error("Loop 1 leak detected liters: {}".format(leaked_liters)) def __loop2_leaktest_result(self, leaked_liters): if leaked_liters > 0: self.__logger.error("Loop 2 leak detected liters: {}".format(leaked_liters)) #endregion #region Private Methods def __is_hot_water(self): down_limit_value = self.__get_down_limit_temp() temperature = 0 if self.__loop1_temp_dev is not None: temperature = self.__loop1_temp_dev.get_temp() return temperature >= down_limit_value def __thermal_mode_on_change(self, machine): self.__logger.info("Thermal mode: {}".format(machine.get_state())) def __set_thermal_force(self, thermal_force): """ Apply thermal force to the devices. """ # 6. Ако модула на пределната термална сила е по-малък от модула на термалната сила, # тогава Термалата сила = Пределната термала сила if thermal_force > abs(self.__thermal_force_limit): thermal_force = self.__thermal_force_limit elif thermal_force < -abs(self.__thermal_force_limit): thermal_force = -abs(self.__thermal_force_limit) # 7. Лимитираме Термалната сила в интервала -100 : + 100: if thermal_force < -100: thermal_force = -100 if thermal_force > 100: thermal_force = 100 self.__logger.debug("Mode: {}; TForce: {:3.3f}"\ .format(self.__thermal_mode.get_state(), thermal_force)) if self.__thermal_mode.is_state(ThermalMode.ColdSeason): if thermal_force > 0: self.__loop1_valve_dev.target_position = 0 self.__loop2_valve_dev.target_position = 0 elif thermal_force <= 0: self.__loop1_valve_dev.target_position = 100 self.__loop2_valve_dev.target_position = 100 elif self.__thermal_mode.is_state(ThermalMode.TransisionSeason): if thermal_force < 0: self.__loop1_valve_dev.target_position = 100 self.__loop2_valve_dev.target_position = 0 elif thermal_force > 0: self.__loop1_valve_dev.target_position = 0 self.__loop2_valve_dev.target_position = 100 else: self.__loop1_valve_dev.target_position = 0 self.__loop2_valve_dev.target_position = 0 elif self.__thermal_mode.is_state(ThermalMode.WarmSeason): if thermal_force < 0: self.__loop1_valve_dev.target_position = 100 self.__loop2_valve_dev.target_position = 100 elif thermal_force > 0: self.__loop1_valve_dev.target_position = 0 self.__loop2_valve_dev.target_position = 0 # If thermal mode set properly apply thermal force if not self.__thermal_mode.is_state(ThermalMode.NONE): self.__set_ventilation(thermal_force) # Set convector fan. conv_tf = l_scale(thermal_force, [0, 100], [0, 3]) conv_tf = abs(conv_tf) conv_tf = int(conv_tf) self.__convector_dev.set_state(conv_tf) #endregion #region Protected Methods def _init(self): """Initialize the module. """ self.__logger = get_logger(__name__) self.__logger.info("Starting up the {} {}".format(self.name, self.__identifier)) # Create thermal mode. self.__thermal_mode = StateMachine(ThermalMode.NONE) self.__thermal_mode.on_change(self.__thermal_mode_on_change) # Create update timer. self.__update_timer = Timer(5) # Stop timer. self.__stop_timer = Timer(10) # Create temperature processor. self.__temp_proc = TemperatureProcessor() # Create temperature queue. self.__queue_temperatures = deque([], maxlen=20) # Create registers callbacks. self.__init_registers() # Shutting down all the devices. self.__set_thermal_force(0) def _update(self): """ Update cycle. """ # Update thermometres values. self.__update_thermometers_values() # Update occupation flags. is_empty = self.__is_empty() # If the window is opened, just turn off the HVAC. window_tamper_state = self.__read_window_tamper() # If temperature is less then 10 deg on loop 1. is_hot_water = self.__is_hot_water() # Take all necessary condition for normal operation of the HVAC. # stop_flag = (not is_empty or not window_tamper_state or not is_hot_water) stop_flag = False if stop_flag: self.__stop_timer.update() if self.__stop_timer.expired: self.__stop_timer.clear() if self.__stop_flag != stop_flag: self.__stop_flag = stop_flag self.__set_thermal_force(0) if not stop_flag: self.__stop_flag = False self.__stop_timer.update_last_time() # Main update rate at ~ 20 second. # На всеки 20 секунди се правят следните стъпки: self.__update_timer.update() if self.__update_timer.expired and not self.__stop_flag: self.__update_timer.clear() # Recalculate the temperatures. self.__temp_proc.update() crg_temp = 0 expected_room_temp = 0 # Update current room temperature. temperature = self.temperature # Add temperature to the queue. self.__queue_temperatures.append(temperature) self.__logger.debug("ROOM: {:3.3f}".format(temperature)) # 1. Изчислява се целевата температура на стаята: goal_room_temp = self.__goal_building_temp + self.__adjust_temp # 2. Изчислява се очакваната температура на стаята: expected_room_temp = temperature + self.__delta_temp # 3. Намира се коригиращата разлика между # Целевата температура и Очакваната температура на стаята: crg_temp = goal_room_temp - expected_room_temp self.__logger.debug("GRT {:3.3f}\t ERT {:3.3f}\t CRG: {:3.3f}"\ .format(goal_room_temp, expected_room_temp, crg_temp)) # 4. # Колкото е по-отрицателна температурата, толкова повече трябва да охлаждаме, # колкото е по-положителна - толкова повече трябва да отопляваме. # Определяме минималната термална сила, на база Корекционната температура: #self.__thermal_force_limit = aprox(-5 => -100, 5 => 100) # 5. Интегрираме термалната сила: self.__thermal_force += crg_temp # Apply the integrated force. self.__set_thermal_force(self.__thermal_force) if self.__loop1_valve_dev is not None: if self.__loop1_valve_dev.current_position <= 0: if self.__loop1_leak_test is not None: self.__loop1_leak_test.run() if self.__loop2_valve_dev is not None: if self.__loop2_valve_dev.current_position <= 0: if self.__loop2_leak_teat is not None: self.__loop2_leak_teat.run() self._registers.write("{}.temp_{}.actual".format(self.key, self.__identifier), temperature) # Recalculate delta time. # pass_time = time.time() - self.__lastupdate_delta_time # if pass_time > self.__delta_time: # self.__delta_temp = temperature - self.__delta_temp # self.__logger.debug("DT: {:3.3f}".format(self.__delta_temp)) # # Update current time. # self.__lastupdate_delta_time = time.time() self.__loop1_valve_dev.update() self.__loop2_valve_dev.update() self.__convector_dev.update() def _shutdown(self): """Shutdown the tamper. """ self.__logger.info("Shutting down the {} {}".format(self.name, self.__identifier)) self.__set_thermal_force(0) if not self.__loop1_valve_dev is None: self.__loop1_valve_dev.shutdown() if not self.__loop2_valve_dev is None: self.__loop2_valve_dev.shutdown() if not self.__convector_dev is None: self.__convector_dev.shutdown()
class MODV1(BaseBlind): """Electronic blinds""" #region Attributes __input_fb = verbal_const.OFF """Input feedback.""" __output_ccw = verbal_const.OFF """Output CCW""" __output_cw = verbal_const.OFF """Output CW""" __deg_per_sec = 0 """Degreases per sec.""" __feedback_treshold = 0 """Feedback threshold.""" __blinds_state = None """Blinds state.""" __move_timer = None """Move timer.""" __current_position = 0 """Current position of the blinds.""" __new_position = 0 """New position of the blinds.""" __calibration_state = None """"Calibration state.""" __feedback_type = FeedbackType.NONE """Feedback type.""" __timout_counter = 0 """Timeout counter.""" __t1 = 0 """T1 moment of first limit.""" __t2 = 0 """T2 moment of second limit.""" #endregion #region constructor / Destructor def __init__(self, **config): """Constructor""" super().__init__(config) self._vendor = "POLYGONTeam" self._model = "Blind" self.__logger = get_logger(__name__) self.__blinds_state = StateMachine(BlindsState.Wait) self.__move_timer = Timer() self.__calibration_state = StateMachine(CalibrationState.NONE) options = self._config["options"] if "output_cw" in options: self.__output_cw = options["output_cw"] if "output_ccw" in options: self.__output_ccw = options["output_ccw"] if "feedback" in options: self.__input_fb = options["feedback"] if "feedback_tresh" in options: self.__feedback_treshold = options["feedback_tresh"] if "deg_per_sec" in options: self.__deg_per_sec = options["deg_per_sec"] # Set the feedback type. if self.__input_fb.startswith("AI"): self.__feedback_type = FeedbackType.Analog elif self.__input_fb.startswith("DI"): self.__feedback_type = FeedbackType.Digital else: GlobalErrorHandler.log_missing_resource(self.__logger, "{}: feedback not set correct or incomparable. Check feedback settings.".format(self.name)) #endregion #region Private Methods (PLC I/O) def __stop(self): """Stop the engine.""" if self._controller.is_valid_gpio(self.__output_cw): self._controller.digital_write(self.__output_cw, 0) if self._controller.is_valid_gpio(self.__output_ccw): self._controller.digital_write(self.__output_ccw, 0) def __turn_cw(self): """Turn the motor CW.""" if self._controller.is_valid_gpio(self.__output_cw): self._controller.digital_write(self.__output_cw, 1) if self._controller.is_valid_gpio(self.__output_ccw): self._controller.digital_write(self.__output_ccw, 0) def __turn_ccw(self): """Turn the motor CCW.""" if self._controller.is_valid_gpio(self.__output_cw): self._controller.digital_write(self.__output_cw, 0) if self._controller.is_valid_gpio(self.__output_ccw): self._controller.digital_write(self.__output_ccw, 1) def __reed_fb(self): """Read feedback.""" fb_value = 0 if not self._controller.is_valid_gpio(self.__input_fb): return fb_value if self.__feedback_type == FeedbackType.Digital: fb_value = self._controller.digital_read(self.__input_fb) if self.__feedback_type == FeedbackType.Analog: ai = self._controller.analog_read(self.__input_fb) value = ai["value"] if value < 0: value = 0 fb_value = value >= self.__feedback_treshold # self.__logger.debug(f"Voltage: {value:02.4f}") return fb_value #endregion #regio Private Methods def __to_time(self, degrees): return degrees / self.__deg_per_sec #endregion #region Public Methods def init(self): self.__stop() def update(self): if self.__blinds_state.is_state(BlindsState.Prepare): delta_pos = self.__new_position - self.__current_position if delta_pos == 0: self.__stop() self.__blinds_state.set_state(BlindsState.Wait) return time_to_move = self.__to_time(abs(delta_pos)) self.__logger.debug("Time: {}".format(time_to_move)) self.__move_timer.expiration_time = time_to_move self.__move_timer.update_last_time() if delta_pos > 0: self.__turn_cw() elif delta_pos < 0: self.__turn_ccw() self.__blinds_state.set_state(BlindsState.Execute) elif self.__blinds_state.is_state(BlindsState.Execute): self.__move_timer.update() if self.__move_timer.expired: self.__move_timer.clear() self.__stop() self.__current_position = self.__new_position self.__blinds_state.set_state(BlindsState.Wait) input_fb = self.__reed_fb() if input_fb: self.__stop() GlobalErrorHandler.log_hardware_limit(self.__logger, "{} has raised end position.".format(self.name)) self.__current_position = self.__new_position self.__blinds_state.set_state(BlindsState.Wait) elif self.__blinds_state.is_state(BlindsState.Calibrate): if self.__calibration_state.is_state(CalibrationState.Stop): self.__stop() self.__current_position = 0 self.__new_position = 0 self.__calibration_state.set_state(CalibrationState.TurnCW) elif self.__calibration_state.is_state(CalibrationState.TurnCW): self.__turn_cw() self.__calibration_state.set_state(CalibrationState.WaitCurStabCW) elif self.__calibration_state.is_state(CalibrationState.WaitCurStabCW): if self.__timout_counter >= 5: self.__timout_counter = 0 self.__calibration_state.set_state(CalibrationState.WaitLimitCW) else: self.__timout_counter += 1 elif self.__calibration_state.is_state(CalibrationState.WaitLimitCW): fb_value = self.__reed_fb() if fb_value: self.__stop() self.__t1 = time.time() self.__calibration_state.set_state(CalibrationState.TurnCCW) elif self.__calibration_state.is_state(CalibrationState.TurnCCW): self.__turn_ccw() self.__calibration_state.set_state(CalibrationState.WaitCurStabCCW) elif self.__calibration_state.is_state(CalibrationState.WaitCurStabCCW): if self.__timout_counter >= 5: self.__timout_counter = 0 self.__calibration_state.set_state(CalibrationState.WaitLimitCCW) else: self.__timout_counter += 1 elif self.__calibration_state.is_state(CalibrationState.WaitLimitCCW): fb_value = self.__reed_fb() if fb_value: self.__stop() self.__t2 = time.time() time_delta = self.__t2 - self.__t1 time_delta -= 4 self.__deg_per_sec = 180 / time_delta self.__logger.info("DPS: {}".format(self.__deg_per_sec)) self.__turn_cw() self.__calibration_state.set_state(CalibrationState.ExitTightness) elif self.__calibration_state.is_state(CalibrationState.ExitTightness): if self.__timout_counter >= 12: self.__timout_counter = 0 self.__stop() self.__calibration_state.set_state(CalibrationState.WaitCurrentStab) else: self.__timout_counter += 1 elif self.__calibration_state.is_state(CalibrationState.WaitCurrentStab): if self.__timout_counter >= 4: self.__timout_counter = 0 self.__calibration_state.set_state(CalibrationState.GoTo90) else: self.__timout_counter += 1 elif self.__calibration_state.is_state(CalibrationState.GoTo90): self.__calibration_state.set_state(CalibrationState.NONE) self.__set_position(90) return def shutdown(self): self.set_position(0) while not self.__blinds_state.is_state(BlindsState.Wait): self.update() pass def set_position(self, position): """Set position of the blinds. Args: position (float): Position of the blinds. """ if self.__new_position == position: return if position > 180: position = 180 elif position < 0: position = 0 self.__new_position = position self.__blinds_state.set_state(BlindsState.Prepare)
class ElectricalPerformance: """Electrical performance test.""" #region Attributes __test_state = None """Test state machine.""" __check_timer = None """Check test timer.""" __result_cb = None """Callback result device.""" __target_device = None """Target device.""" __registers = None """Registers""" __consumption_1 = 0 __consumption_2 = 0 #endregion #region Constructor / Destructor def __init__(self): """Constructor""" self.__test_state = StateMachine(TestState.NONE) self.__check_timer = Timer(0) self.__registers = Registers.from_csv() def __del__(self): """Destructor""" if self.__check_timer is not None: del self.__check_timer if self.__test_state is not None: del self.__test_state #endregion #region Private Methods def __turn_target_off(self): if isinstance(self.__target_device, F3P146EC072600): self.__target_device.set_speed(0) def __turn_target_on(self): if isinstance(self.__target_device, F3P146EC072600): self.__target_device.set_speed(10) def __take_current_consumption(self): current_power = 0 register = self.__registers.by_name("monitoring.pa.l1") if register is not None: register_data = json.loads(register.value) current_power = register_data["Current"] return current_power #endregion #region Public Methods def start(self): """Start the test.""" self.__test_state.set_state(TestState.Init) def stop(self): """Start the test.""" self.__turn_target_off() self.__test_state.set_state(TestState.NONE) def set_test_target(self, target_device): """Set test target device.""" if target_device is None: return self.__target_device = target_device if isinstance(self.__target_device, F3P146EC072600): self.__target_device.max_speed = 10 self.__target_device.min_speed = 0 def on_result(self, callback): """On result registration.""" if callback is None: return self.__result_cb = callback def run(self): """Run the test.""" if self.__test_state.is_state(TestState.Init): self.__turn_target_off() self.__consumption_1 = 0 self.__consumption_2 = 0 self.__test_state.set_state(TestState.TakeFirstConsumption) elif self.__test_state.is_state(TestState.TakeFirstConsumption): self.__consumption_1 = self.__take_current_consumption() self.__test_state.set_state(TestState.TurnTargetOn) elif self.__test_state.is_state(TestState.TurnTargetOn): self.__turn_target_on() self.__check_timer.expiration_time = 10 self.__check_timer.update_last_time() self.__test_state.set_state(TestState.Wait) elif self.__test_state.is_state(TestState.Wait): self.__check_timer.update() if self.__check_timer.expired: self.__check_timer.clear() self.__test_state.set_state(TestState.TakeSecondConsumption) elif self.__test_state.is_state(TestState.TakeSecondConsumption): self.__consumption_1 = self.__take_current_consumption() self.__test_state.set_state(TestState.TurnTargetOff) elif self.__test_state.is_state(TestState.TurnTargetOff): self.__turn_target_off() self.__test_state.set_state(TestState.ReturnResults) elif self.__test_state.is_state(TestState.ReturnResults): consuption_w = self.__consumption_2 - self.__consumption_1 if self.__result_cb is not None: self.__result_cb(consuption_w) self.__test_state.set_state(TestState.NONE)
class Zone(BasePlugin): """Security zone definition class.""" #region Attribute __logger = None """Logger""" __identifier = 0 """Security zone identifier.""" __allowed_attendant = [] """Allowed attendant.""" __free_to_lock = 0 """Free to lock flag.""" __open_door_flag = 0 """Open door flag.""" __open_timer = None """Open timer.""" __presence_timer = None """Presence timer""" __on_card_cb = None """Reader read callback""" __entry_reader = None """Entry card reader.""" __exit_reader = None """Exit card reader.""" __exit_btn_input = verbal_const.OFF # "DI0" """Exit button input.""" __window_closed_input = verbal_const.OFF """Window closed sensor input.""" __door_closed_input = verbal_const.OFF """Door closed sensor input.""" __pir_input = verbal_const.OFF """PIR closed sensor input.""" __lock_mechanism_output = verbal_const.OFF # "DO0" """Locking mechanism output.""" __door_window_blind_output = verbal_const.OFF """Door window blind output.""" #endregion #region Constructor / Destructor def __init__(self, **config): """Constructor""" super().__init__(config) if "identifier" in config: self.__identifier = config["identifier"] def __del__(self): """Destructor""" if self.__entry_reader is not None: del self.__entry_reader if self.__exit_reader is not None: del self.__exit_reader if self.__open_timer is not None: del self.__open_timer super().__del__() if self.__logger is not None: del self.__logger #endregion #region Private Methods (Registers) def __entry_reader_cb(self, register): # Check data type. if not register.data_type == "json": GlobalErrorHandler.log_bad_register_value(self.__logger, register) return # Create if register.value != {} and self.__entry_reader is None: self.__entry_reader = CardReaderFactory.create( controller=self._controller, name="Entry Reader", vendor=register.value['vendor'], model=register.value['model'], options=register.value['options']) if self.__entry_reader is not None: if self.__entry_reader.reader_state == CardReaderState.NONE: self.__entry_reader.cb_read_card(self.__cb_read_card) self.__entry_reader.init() # Delete elif register.value == {} and self.__entry_reader is not None: self.__delete_reader(self.__entry_reader) def __exit_reader_cb(self, register): # Check data type. if not register.data_type == "json": GlobalErrorHandler.log_bad_register_value(self.__logger, register) return # Create if register.value != {} and self.__exit_reader is None: self.__exit_reader = CardReaderFactory.create( controller=self._controller, name="Exit Reader", vendor=register.value['vendor'], model=register.value['model'], options=register.value['options']) if self.__exit_reader is not None: if self.__exit_reader.reader_state == CardReaderState.NONE: self.__exit_reader.cb_read_card(self.__cb_read_card) self.__exit_reader.init() # Delete elif register.value == {} and self.__exit_reader is not None: self.__delete_reader(self.__exit_reader) def __exit_btn_input_cb(self, register): # Check data type. if not register.data_type == "str": GlobalErrorHandler.log_bad_register_value(self.__logger, register) return self.__exit_btn_input = register.value def __window_closed_input_cb(self, register): # Check data type. if not register.data_type == "str": GlobalErrorHandler.log_bad_register_value(self.__logger, register) return self.__window_closed_input = register.value def __door_closed_input_cb(self, register): # Check data type. if not register.data_type == "str": GlobalErrorHandler.log_bad_register_value(self.__logger, register) return self.__door_closed_input = register.value def __pir_input_cb(self, register): # Check data type. if not register.data_type == "str": GlobalErrorHandler.log_bad_register_value(self.__logger, register) return self.__pir_input = register.value def __lock_mechanism_output_cb(self, register): # Check data type. if not register.data_type == "str": GlobalErrorHandler.log_bad_register_value(self.__logger, register) return self.__lock_mechanism_output = register.value def __door_window_blind_output_cb(self, register): # Check data type. if not register.data_type == "str": GlobalErrorHandler.log_bad_register_value(self.__logger, register) return self.__door_window_blind_output = register.value def __time_to_open_cb(self, register): if not register.data_type == "int": GlobalErrorHandler.log_bad_register_value(self.__logger, register) return self.__open_timer.expiration_time = register.value def __is_empty_timeout_cb(self, register): # Check data type. if not register.data_type == "int": GlobalErrorHandler.log_bad_register_data_type( self.__logger, register) return self.__presence_timer.expiration_time = register.value def __door_window_blind_value_cb(self, register): # Check data type. if not register.data_type == "bool": GlobalErrorHandler.log_bad_register_value(self.__logger, register) return self.__set_door_window_blind(register.value) def __init_registers(self): # Get time to open the latch. time_to_open = self._registers.by_name("{}.time_to_open_{}".format( self.key, self.__identifier)) if time_to_open is not None: time_to_open.update_handlers = self.__time_to_open_cb time_to_open.update() # Is empty timeout. is_empty_timeout = self._registers.by_name("envm.is_empty_timeout") if is_empty_timeout is not None: is_empty_timeout.update_handlers = self.__is_empty_timeout_cb is_empty_timeout.update() # Entry reader. entry_reader_enabled = self._registers.by_name( "{}.entry_reader_{}.enabled".format(self.key, self.__identifier)) if entry_reader_enabled is not None: entry_reader_enabled.update_handlers = self.__entry_reader_cb # Exit reader. exit_reader_enabled = self._registers.by_name( "{}.exit_reader_{}.enabled".format(self.key, self.__identifier)) if exit_reader_enabled is not None: exit_reader_enabled.update_handlers = self.__exit_reader_cb # Create exit button. exit_button_input = self._registers.by_name( "{}.exit_button_{}.input".format(self.key, self.__identifier)) if exit_button_input is not None: exit_button_input.update_handlers = self.__exit_btn_input_cb exit_button_input.update() # Create window closed sensor. window_closed_input = self._registers.by_name( "{}.window_closed_{}.input".format(self.key, self.__identifier)) if window_closed_input is not None: window_closed_input.update_handlers = self.__window_closed_input_cb window_closed_input.update() # Create door closed sensor. door_closed_input = self._registers.by_name( "{}.door_closed_{}.input".format(self.key, self.__identifier)) if door_closed_input is not None: door_closed_input.update_handlers = self.__door_closed_input_cb door_closed_input.update() # Create PIR sensor. pir_input = self._registers.by_name("{}.pir_{}.input".format( self.key, self.__identifier)) if pir_input is not None: pir_input.update_handlers = self.__pir_input_cb pir_input.update() # Create locking mechanism. lock_mechanism_output = self._registers.by_name( "{}.lock_mechanism_{}.output".format(self.key, self.__identifier)) if lock_mechanism_output is not None: lock_mechanism_output.update_handlers = self.__lock_mechanism_output_cb lock_mechanism_output.update() # Door window blind. door_window_blind_output = self._registers.by_name( "{}.door_window_blind_{}.output".format(self.key, self.__identifier)) if door_window_blind_output is not None: door_window_blind_output.update_handlers = self.__door_window_blind_output_cb door_window_blind_output.update() # Door window blind. self._registers.add_callback("{}.door_window_blind_{}.value".format( self.key, self.__identifier), self.__door_window_blind_value_cb, update=True) def __set_zone_occupied(self, value): reg_occupation = self._registers.by_name("{}.zone_{}_occupied".format( self.key, self.__identifier)) if reg_occupation is not None: if reg_occupation.value != value: reg_occupation.value = value #endregion #region Private Methods (Card Reader) def __check_card_state(self, card_id): # Request - Eml6287 card_state = CardState.NONE allowed_len = len(self.__allowed_attendant) checked_index = 0 for card in self.__allowed_attendant: if card["card_id"] == card_id: now = time.time() if now < card["valid_until"]: card_state = CardState.Allowed break else: card_state = CardState.Expired break checked_index += 1 if checked_index == allowed_len and card_state == CardState.NONE: card_state = CardState.NotAllowed return card_state def __cb_read_card(self, card_id, serial_number): # Set flag to open the door. card_state = self.__check_card_state(card_id) if card_state == CardState.Allowed: if self.__open_door_flag == 0: self.__open_door_flag = 1 if self.__on_card_cb is not None: self.__on_card_cb(card_id, serial_number, card_state.value) def __delete_reader(self, reader): if reader is not None: reader.shutdown() while reader.reader_state == CardReaderState.RUN: pass del reader def __update_entry_reader(self): """Update entry reader state.""" if self.__entry_reader is not None: # Update card reader. self.__entry_reader.update() if self.__entry_reader.reader_state == CardReaderState.STOP: message = "Card reader {}; State {}; Port {}."\ .format(self.__entry_reader.serial_number, \ self.__entry_reader.reader_state, \ self.__entry_reader.port_name) GlobalErrorHandler.log_hardware_malfunction( self.__logger, message) self.__entry_reader.init() if self.__entry_reader.reader_state == CardReaderState.NONE: message = "Card reader {}; State {}."\ .format(self.__entry_reader.serial_number, self.__entry_reader.reader_state) GlobalErrorHandler.log_hardware_malfunction( self.__logger, message) self.__entry_reader.init() def __update_exit_reader(self): """Update exit reader state.""" if self.__exit_reader is not None: # Update card reader. self.__exit_reader.update() if self.__exit_reader.reader_state == CardReaderState.STOP: message = "Card reader {}; State {}; Port {}."\ .format(self.__exit_reader.serial_number, \ self.__exit_reader.reader_state, \ self.__exit_reader.port_name) GlobalErrorHandler.log_hardware_malfunction( self.__logger, message) self.__exit_reader.init() if self.__exit_reader.reader_state == CardReaderState.NONE: message = "Card reader {}; State {}."\ .format(self.__exit_reader.serial_number, self.__entry_reader.reader_state) GlobalErrorHandler.log_hardware_malfunction( self.__logger, message) self.__exit_reader.init() #endregion #region (PLC) def __read_exit_button(self): state = False if self._controller.is_valid_gpio(self.__exit_btn_input): state = self._controller.digital_read(self.__exit_btn_input) return state def __read_window_tamper(self): state = False if self._controller.is_valid_gpio(self.__window_closed_input): state = self._controller.digital_read(self.__window_closed_input) return state def __read_door_tamper(self): state = False if self._controller.is_valid_gpio(self.__door_closed_input): state = self._controller.digital_read(self.__door_closed_input) return state def __read_pir_sensor(self): state = False if self._controller.is_valid_gpio(self.__pir_input): state = self._controller.digital_read(self.__pir_input) return state def __set_lock_mechanism(self, value=0): if self._controller.is_valid_gpio(self.__lock_mechanism_output): self._controller.digital_write(self.__lock_mechanism_output, value) def __set_door_window_blind(self, value=0): if self._controller.is_valid_gpio(self.__door_window_blind_output): self._controller.digital_write(self.__door_window_blind_output, value) #endregion #region Protected Methods def _init(self): """Initialize the plugin. """ # Create logger. self.__logger = get_logger(__name__) self.__logger.info("Starting up the {} {}".format( self.name, self.__identifier)) # Setup open timer to 10 seconds. self.__open_timer = Timer(10) # Setup presence timer to 60 seconds. self.__presence_timer = Timer(60) self.__init_registers() def _update(self): """Update the plugin. """ # Update inputs. btn_state = self.__read_exit_button() door_tamper_state = self.__read_door_tamper() pir_sensor_state = self.__read_pir_sensor() window_tamper_state = self.__read_window_tamper() self.__update_entry_reader() self.__update_exit_reader() # Check if the button is pressed. if btn_state: if self.__open_door_flag == 0: self.__open_door_flag = 1 # Check if the flag is raise. if self.__open_door_flag == 1: self.__set_lock_mechanism(1) self.__set_door_window_blind(1) self.__open_timer.update_last_time() self.__open_door_flag = 0 self.__free_to_lock = 1 # Lock the door after when it is closed. if door_tamper_state: if self.__free_to_lock == 1: self.__set_lock_mechanism(0) self.__set_door_window_blind(0) self.__free_to_lock = 0 # Check is it time to close the latch. if self.__open_door_flag == 0: self.__open_timer.update() if self.__open_timer.expired: self.__open_timer.clear() if self.__free_to_lock == 1: self.__set_lock_mechanism(0) self.__set_door_window_blind(0) self.__free_to_lock = 0 # If one of the sensor are activated, set occupation flag to true. if door_tamper_state or pir_sensor_state or window_tamper_state: self.__presence_timer.update_last_time() self.__set_zone_occupied(True) # Else, run the expiration timer. else: self.__presence_timer.update() if self.__presence_timer.expired: self.__presence_timer.clear() self.__set_zone_occupied(False) def _shutdown(self): """Shutting down the plugin. """ self.__logger.info("Shutting down the {} {}".format( self.name, self.__identifier)) self.__set_lock_mechanism(0) self.__set_door_window_blind(0) self.__delete_reader(self.__entry_reader) self.__delete_reader(self.__exit_reader) #endregion #region Public Methods def on_card(self, callback): """Set reader read calback.""" if callback is None: return self.__on_card_cb = callback def add_allowed_attendant(self, card_id): """Add allowed attendant ID. Parameters ---------- ids : str Cards id. """ if card_id is None: return if card_id == "": return if card_id in self.__allowed_attendant: pass else: self.__allowed_attendant.append(card_id) def add_allowed_attendees(self, ids): """Add allowed attendees IDs. Parameters ---------- ids : array Cards id. """ if ids is None: return self.__allowed_attendant.clear() for key in ids: record = ids[key] self.add_allowed_attendant(record)