class TemplatePlugin(BasePlugin): """Template plugin controller.""" #region Attributes __logger = None """Logger""" __update_timer = None """Update timer.""" #endregion #region Private Methods def __do_job(self): """Do the JOB method.""" self.__logger.info("Do some job.") #endregion #region Public Methods def _init(self): """Initialize the plugin. """ self.__logger = get_logger(__name__) self.__logger.info("Starting up the {}".format(self.name)) self.__update_timer = Timer(1) def _update(self): """Update the plugin. """ # Update the timer. self.__update_timer.update() if self.__update_timer.expired: self.__update_timer.clear() self.__do_job() def _shutdown(self): """Shutting down the plugin. """ self.__logger.info("Shutting down the {}".format(self.name))
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 Alarm(BasePlugin): """Blinds controller device.""" #region Attributes __logger = None """Logger""" __update_timer = None """Update timer.""" #endregion #region Private Methods (Registers Interface) def __sound_device_settings_cb(self, register: Register): # TODO: - Call the visual device factory. # TODO: - Build the visual device interface. # TODO: - Check is it possible to build. self.__logger.debug("Init the sound device.") def __visual_device_settings_cb(self, register: Register): # TODO: - Call the visual device factory. # TODO: - Build the visual device interface. # TODO: - Check is it possible to build. self.__logger.debug("Init the visual device.") def __init_registers(self): visual_device_settings = self._registers.by_name( "{}.device.visual.settings".format(self.key)) if visual_device_settings is not None: visual_device_settings.update_handlers = self.__visual_device_settings_cb visual_device_settings.update() sound_device_settings = self._registers.by_name( "{}.device.sound.settings".format(self.key)) if sound_device_settings is not None: sound_device_settings.update_handlers = self.__sound_device_settings_cb sound_device_settings.update() #endregion #region Private Methods def __do_job(self): """Do the JOB method.""" self.__logger.info("Do some job.") #endregion #region Public Methods def _init(self): """Initialize the plugin. """ self.__logger = get_logger(__name__) self.__logger.info("Starting up the {}".format(self.name)) self.__update_timer = Timer(1) self.__init_registers() def _update(self): """Update the plugin. """ # Update the timer. self.__update_timer.update() if self.__update_timer.expired: self.__update_timer.clear() self.__do_job() def _shutdown(self): """Shutting down the plugin. """ self.__logger.info("Shutting down the {}".format(self.name))
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 Statistics(BasePlugin): """Template plugin controller.""" #region Attributes __logger = None """Logger""" __update_timer = None """Update timer.""" __light_sensor = None """Light sensor target. """ #endregion #region Private Methods def __get_todays_path(self, name=""): # Current file path. cwf = os.path.dirname(os.path.abspath(__file__)) # Statistics path. full_dir_path = os.path.join(cwf, "..", "..", "..", "statistics") # Crete log directory. if not os.path.exists(full_dir_path): os.makedirs(full_dir_path) # Todays filename. todays_file_name = strftime("%Y%m%d_%H%M%S", gmtime()) if not name != "": todays_file_name += "_" todays_file_name += name todays_file_name += ".log" # File name. file_name = os.path.join(full_dir_path, todays_file_name) return file_name def __create_logger(self, name, level=logging.INFO): """To setup as many loggers as you want""" log_file = self.__get_todays_path(name) # Formater # log_format = "%(asctime)s\t%(levelname)s\t%(name)s\t%(lineno)s\t%(message)s" log_format = "%(asctime)s\t%(name)s\t%(message)s" formatter = logging.Formatter(log_format) # Handler handler = logging.FileHandler(log_file) handler.setFormatter(formatter) # Logger logger = logging.getLogger(name) logger.setLevel(level) logger.addHandler(handler) return logger def __periodic_job(self): """Do the JOB method.""" if self.__light_sensor is not None: self.__light_sensor.update() measured_value = self.__light_sensor.get_value() if self.__light_sensor_logger is not None: self.__light_sensor_logger.info("{}".format(measured_value)) #endregion #region Private Methods (Registers Interface) def __light_sensor_settings_cb(self, register: Register): # Check data type. if not register.data_type == "json": GlobalErrorHandler.log_bad_register_value(self.__logger, register) return if register.value != None and self.__light_sensor is None: dev_name = "{} {}".format("Test sensor", self.name) self.__light_sensor = LuxmeterFactory.create( name=dev_name, controller=self._controller, vendor=register.value['vendor'], model=register.value['model'], options=register.value['options']) if self.__light_sensor is not None: self.__light_sensor.init() def __init_registers(self): sensor_enabled = self._registers.by_name("light.sensor.settings") if sensor_enabled is not None: sensor_enabled.update_handlers = self.__light_sensor_settings_cb sensor_enabled.update() #endregion #region Public Methods def _init(self): """Initialize the plugin. """ self.__logger = get_logger(__name__) self.__logger.info("Starting up the {}".format(self.name)) self.__update_timer = Timer(1) self.__light_sensor_logger = self.__create_logger("LightSensor") self.__init_registers() def _update(self): """Update the plugin. """ # Update the timer. self.__update_timer.update() if self.__update_timer.expired: self.__update_timer.clear() self.__periodic_job() def _shutdown(self): """Shutting down the plugin. """ self.__logger.info("Shutting down the {}".format(self.name))
class Sys(BasePlugin): """Blinds controller device.""" #region Attributes __logger = None """Logger""" __led_state = 0 """LED State.""" __blink_timer = None """Update timestamp.""" __led_out = verbal_const.OFF """LED Output""" __collission_timer = None """Update timer.""" __rules = None """Rules""" __enable_info_msg = True """Enable info messages.""" __enable_wrn_msg = True """Enable warning messages.""" __enable_err_msg = True """Enable error messages.""" __disc_status_timer = None """Disc check timer.""" #endregion #region Destructor def __del__(self): if self.__blink_timer is not None: del self.__blink_timer super().__del__() if self.__logger is not None: del self.__logger #endregion #region Private Methods (Status LED) def __set_led(self, state): if self._controller.is_valid_gpio(self.__led_out): self._controller.set_led(self.__led_out, state) #endregion #region Private Methods (Colision Detection) def __setup_rules(self): self.__rules = Rules() # GPIOs for index in range(12): self.__rules.add(Rule("DO{}".format(index), MonitoringLevel.Error)) self.__rules.add(Rule("DI{}".format(index), MonitoringLevel.Warning)) self.__rules.add(Rule("AO{}".format(index), MonitoringLevel.Error)) self.__rules.add(Rule("AI{}".format(index), MonitoringLevel.Warning)) self.__rules.add(Rule("RO{}".format(index), MonitoringLevel.Error)) # 1 Wire devices ow_devices = self._controller.get_1w_devices() for ow_device in ow_devices: self.__rules.add(Rule(ow_device["circuit"], MonitoringLevel.Info)) # Serial Ports if os.name == "nt": for index in range(1, 11): self.__rules.add(Rule("COM{}".format(index), MonitoringLevel.Error)) elif os.name == "posix": for index in range(0, 11): self.__rules.add(Rule("/dev/ttyS{}".format(index), MonitoringLevel.Error)) self.__rules.add(Rule("/dev/ttyUSB{}".format(index), MonitoringLevel.Error)) self.__rules.add(Rule("/dev/ttyACM{}".format(index), MonitoringLevel.Error)) # Add event. self.__rules.on_event(self.__on_event) def __on_event(self, intersections, rule: Rule): """On event callback for collisions.""" level = MonitoringLevel(rule.level) if level == MonitoringLevel.Debug: self.__logger.debug("Debug") self.__logger.debug(intersections) if level == MonitoringLevel.Info and self.__enable_info_msg: self._registers.write("{}.col.info_message".format(self.key), intersections) if level == MonitoringLevel.Warning and self.__enable_wrn_msg: self._registers.write("{}.col.warning_message".format(self.key), intersections) if level == MonitoringLevel.Error and self.__enable_err_msg: self._registers.write("{}.col.error_message".format(self.key), intersections) #endregion #region Private Methods (Registers Interface) def __blink_time_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 self.__blink_timer.expiration_time = register.value def __led_out_cb(self, register): # Check data type. if not register.data_type == "str": GlobalErrorHandler.log_bad_register_data_type(self.__logger, register) return self.__led_out = register.value def __clear_errors_cb(self, register): """Clear errors callback.""" if register.value == 1: # Clear the flag. register.value = 0 # Clear info messages. self._registers.write("{}.col.info_message".format(self.key), {}) # Clear warning messages. self._registers.write("{}.col.warning_message".format(self.key), {}) # Clear error messages. self._registers.write("{}.col.error_message".format(self.key), {}) def __enable_info_msg_cb(self, register): # Check data type. if not register.data_type == "bool": GlobalErrorHandler.log_bad_register_data_type(self.__logger, register) return self.__enable_info_msg = register.value def __enable_wrn_msg_cb(self, register): # Check data type. if not register.data_type == "bool": GlobalErrorHandler.log_bad_register_data_type(self.__logger, register) return self.__enable_wrn_msg = register.value def __enable_err_msg_cb(self, register): # Check data type. if not register.data_type == "bool": GlobalErrorHandler.log_bad_register_data_type(self.__logger, register) return self.__enable_err_msg = register.value def __init_registers(self): # Status LED blink time. blink_time = self._registers.by_name(self.key + ".sl.blink_time") if blink_time is not None: blink_time.update_handlers = self.__blink_time_cb blink_time.update() # Status LED output. output = self._registers.by_name(self.key + ".sl.output") if output is not None: output.update_handlers = self.__led_out_cb output.update() clear_errors = self._registers.by_name(self.key + ".col.clear_errors") if clear_errors is not None: clear_errors.update_handlers = self.__clear_errors_cb clear_errors.value = 1 # Enable info messages. enable_info_msg = self._registers.by_name(self.key + ".col.info_message.enable") if enable_info_msg is not None: enable_info_msg.update_handlers = self.__enable_info_msg_cb enable_info_msg.update() # Enable warning messages. enable_wrn_msg = self._registers.by_name(self.key + ".col.warning_message.enable") if enable_wrn_msg is not None: enable_wrn_msg.update_handlers = self.__enable_wrn_msg_cb enable_wrn_msg.update() # Enable error messages. enable_err_msg = self._registers.by_name(self.key + ".col.error_message.enable") if enable_err_msg is not None: enable_err_msg.update_handlers = self.__enable_err_msg_cb enable_err_msg.update() def __update_disc_space(self): total, used, free = disk_size() self._registers.write("sys.disc.total", total) self._registers.write("sys.disc.used", used) self._registers.write("sys.disc.free", free) #endregion #region Public Methods def _init(self): """Initialize the plugin. """ # Create logger. self.__logger = get_logger(__name__) self.__logger.info("Starting up the {}".format(self.name)) # Status LED blink timer. self.__blink_timer = Timer(1) # Colission detection. self.__collission_timer = Timer(1) # Create disc check timer. self.__disc_status_timer = Timer(10) self.__init_registers() self.__setup_rules() def _update(self): """Update the plugin. """ # Update the Blink LED timer. self.__blink_timer.update() if self.__blink_timer.expired: self.__blink_timer.clear() if self.__led_state: self.__led_state = 0 else: self.__led_state = 1 # update the LED state. self.__set_led(self.__led_state) # Update the collision timer. self.__collission_timer.update() if self.__collission_timer.expired: self.__collission_timer.clear() # Check for collision. self.__rules.check(self._registers) # Update disc space. self.__disc_status_timer.update() if self.__disc_status_timer.expired: self.__disc_status_timer.clear() # Update the disck space registers. self.__update_disc_space() def _shutdown(self): """Shutting down the plugin. """ self.__logger.info("Shutting down the {}".format(self.name)) self.__set_led(0)
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 Blind(BasePlugin): """Blind controller device.""" #region Attributes __logger = None """Logger""" __identifier = 0 """Identifier """ __blind_mechanism = None """Blkind mechanism """ __sun_spot_update_timer = None """Sun spot update timer. """ __sun_azm = 0 """Sun azimuth. """ __sun_elev = 0 """Sun elevation. """ __sun_spot_limit = 0 """Sun spot limit. """ __object_height = 0 """Object height [m] """ __zone_occupation = False """Zone occupation flag. """ #endregion #region Constructor / Destructor def __init__(self, **config): """Constructor""" super().__init__(config) if "identifier" in config: self.__identifier = config["identifier"] def __del__(self): """Destructor""" super().__del__() if self.__logger is not None: del self.__logger #endregion #region Private Methods (Registers) def __blind_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.__blind_mechanism is None: self.__blind_mechanism = BlindsFactory.create( controller=self._controller, name="Blind", vendor=register.value['vendor'], model=register.value['model'], options=register.value['options']) if self.__blind_mechanism is not None: self.__blind_mechanism.init() # Delete elif register.value == {} and self.__blind_mechanism is not None: del self.__blind_mechanism def __position_cb(self, register): # Check data type. if not ((register.data_type == "float") or (register.data_type == "int")): GlobalErrorHandler.log_bad_register_value(self.__logger, register) return if self.__blind_mechanism is not None: self.__blind_mechanism.set_position(register.value) def __object_height_cb(self, register): # Check data type. if not ((register.data_type == "float") or (register.data_type == "int")): GlobalErrorHandler.log_bad_register_value(self.__logger, register) return if self.__object_height != register.value: self.__object_height = register.value def __sunspot_limit_cb(self, register): # Check data type. if not ((register.data_type == "float") or (register.data_type == "int")): GlobalErrorHandler.log_bad_register_value(self.__logger, register) return if self.__sun_spot_limit != register.value: self.__sun_spot_limit = register.value def __zone_occupation_cb(self, register): # Check data type. if not ((register.data_type == "float") or (register.data_type == "int") or (register.data_type == "bool")): GlobalErrorHandler.log_bad_register_value(self.__logger, register) return if self.__zone_occupation != register.value: self.__zone_occupation = register.value def __init_registers(self): blind = self._registers.by_name("{}.blind_{}.mechanism".format( self.key, self.__identifier)) if blind is not None: blind.update_handlers = self.__blind_cb blind.update() position = self._registers.by_name("{}.blind_{}.position".format( self.key, self.__identifier)) if position is not None: position.update_handlers = self.__position_cb position.update() object_height = self._registers.by_name( "{}.blind_{}.object_height".format(self.key, self.__identifier)) if object_height is not None: object_height.update_handlers = self.__object_height_cb object_height.update() sunspot_limit = self._registers.by_name( "{}.blind_{}.sunspot_limit".format(self.key, self.__identifier)) if sunspot_limit is not None: sunspot_limit.update_handlers = self.__sunspot_limit_cb sunspot_limit.update() ac_zone_occupied = self._registers.by_name( "ac.zone_{}_occupied".format(self.__identifier)) if ac_zone_occupied is not None: ac_zone_occupied.update_handlers = self.__zone_occupation_cb ac_zone_occupied.update() def __get_sun_pos(self): sun_elev_reg = self._registers.by_name("envm.sun.elevation") if sun_elev_reg is not None: if not ((sun_elev_reg.data_type == "float") or (sun_elev_reg.data_type == "int")): GlobalErrorHandler.log_bad_register_value( self.__logger, sun_elev_reg) return if self.__sun_elev != sun_elev_reg.value: self.__sun_elev = sun_elev_reg.value sun_azm_reg = self._registers.by_name("envm.sun.azimuth") if sun_azm_reg is not None: if not ((sun_azm_reg.data_type == "float") or (sun_azm_reg.data_type == "int")): GlobalErrorHandler.log_bad_register_value( self.__logger, sun_azm_reg) return if self.__sun_azm != sun_azm_reg.value: self.__sun_azm = sun_azm_reg.value #endregion #region Private Methods def __calc_sun_spot(self): if (self.__sun_azm > 0) and (self.__sun_elev > 0): # print(f"Blinds -> Azm: {self.__sun_azm:03.2f}; Elev: {self.__sun_elev:03.2f}") # Calculate the shadow length. shadow_l = shadow_length(self.__object_height, to_rad(self.__sun_elev)) # print(f"Blinds -> Shadow: {shadow_l:03.2f}") theta = 360 - (self.__sun_azm + 180) # print(f"Blinds -> Theta: {theta:03.2f}") # Calculate cartesian x = shadow_l * math.cos(to_rad(abs(theta))) y = shadow_l * math.sin(to_rad(abs(theta))) # print(f"Blinds -> X: {x:03.2f}; Y: {y:03.2f}") is_cloudy = False if (x > self.__sun_spot_limit or y > self.__sun_spot_limit) and not is_cloudy: # If is not cloudy and the sun spot is too big, just close the blinds. if not self.__blind_mechanism is None: self.__blind_mechanism.set_position(0) #endregion #region Public Methods def _init(self): """Initialize the plugin. """ self.__logger = get_logger(__name__) self.__logger.info("Starting up the {} {}".format( self.name, self.__identifier)) self.__sun_spot_update_timer = Timer(2) self.__init_registers() if not self.__blind_mechanism is None: self.__blind_mechanism.set_position(90) def _update(self): """Update the plugin. """ # Update occupation flags. if not self.__zone_occupation: if not self.__blind_mechanism is None: # self.__blind_mechanism.set_position(0) pass self.__sun_spot_update_timer.update() if self.__sun_spot_update_timer.expired: self.__sun_spot_update_timer.clear() self.__get_sun_pos() self.__calc_sun_spot() if not self.__blind_mechanism is None: self.__blind_mechanism.update() def _shutdown(self): """Shutting down the plugin. """ self.__logger.info("Shutting down the {} {}".format( self.name, self.__identifier)) if not self.__blind_mechanism is None: self.__blind_mechanism.shutdown()
class Monitoring(BasePlugin): """Blinds controller device.""" #region Attributes __logger = None """Logger""" __cw_flowmeter_dev = None """Cold water flow meter.""" __cw_leak_test = None """Cold water leak test.""" __hw_flowmeter_dev = None """Hot water flow meter.""" __hw_leak_test = None """Hot water leak test.""" __power_analyser = None """Power analyser.""" __evok_setting = None """EVOK settings.""" __measurements = [] """Power analyser measurements. """ __demand_timer = None """Demand measuring timer. """ #endregion #region Private Methods def __filter_measurements_by_time(self, measurements, time_sec: int): # Create filter list. filtered_measurements = measurements.copy() filtered_measurements.clear() # Reset delete flag. delete_flag = False # Now! time_now = time.time() # Filter all records. for measurement in measurements: # Calculate delta time. delta_t = time_now - measurement["ts"] # Filter if delta_t < time_sec: filtered_measurements.append(measurement) # Else mark for deletion. else: delete_flag = True # Execute the flag. if delete_flag: self.__measurements.clear() for measurement in filtered_measurements: self.__measurements.append(measurement) #endregion #region Private Methods (Cold Water Flowmeter) def __cw_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.__cw_flowmeter_dev is None: self.__cw_flowmeter_dev = FlowmetersFactory.create( name="Cold water flowmeter", controller=self._controller, vendor=register.value['vendor'], model=register.value['model'], options=register.value['options']) if self.__cw_flowmeter_dev is not None: self.__cw_flowmeter_dev.init() # 20 seconds is time for leak testing. self.__cw_leak_test = LeakTest(self.__cw_flowmeter_dev, 20) self.__cw_leak_test.on_result(self.__cw_leaktest_result) elif register.value == {} and self.__cw_flowmeter_dev is not None: self.__cw_flowmeter_dev.shutdown() del self.__cw_flowmeter_dev del self.__cw_leak_test def __cw_leaktest_result(self, leak_liters): if leak_liters > 0: self._registers.write("{}.cw.leak".format(self.key), leak_liters) def __init_cw(self): cw_flowmeter = self._registers.by_name( "{}.cw.flowmeter_settings".format(self.key)) if cw_flowmeter is not None: cw_flowmeter.update_handlers = self.__cw_flowmeter_settings_cb cw_flowmeter.update() def __update_cw(self): fm_state = self._registers.by_name(self.key + ".cw.value") if self.__cw_flowmeter_dev is not None and\ fm_state is not None: fm_state.value = self.__cw_flowmeter_dev.get_liters() # If the zone is empty check for leaks. is_empty = self._registers.by_name("envm.is_empty") if self.__cw_flowmeter_dev is not None and\ is_empty is not None and\ is_empty.value: self.__cw_leak_test.run() #endregion #region Private Methods (Hot Water Flowmeter) def __hw_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.__hw_flowmeter_dev is None: self.__hw_flowmeter_dev = FlowmetersFactory.create( name="Hot water flowmeter", controller=self._controller, vendor=register.value['vendor'], model=register.value['model'], options=register.value['options']) if self.__hw_flowmeter_dev is not None: self.__hw_flowmeter_dev.init() # 20 seconds is time for leak testing. self.__hw_leak_test = LeakTest(self.__hw_flowmeter_dev, 20) self.__hw_leak_test.on_result(self.__hw_leaktest_result) elif register.value == {} and self.__hw_flowmeter_dev is not None: self.__hw_flowmeter_dev.shutdown() del self.__hw_flowmeter_dev del self.__hw_leak_test def __hw_leaktest_result(self, leak_liters): if leak_liters > 0: self._registers.write("{}.hw.leak".format(self.key), leak_liters) def __init_hw(self): hw_flowmeter = self._registers.by_name( "{}.hw.flowmeter_settings".format(self.key)) if hw_flowmeter is not None: hw_flowmeter.update_handlers = self.__hw_flowmeter_settings_cb hw_flowmeter.update() def __update_hw(self): fm_state = self._registers.by_name("{}.hw.value".foamrat(self.key)) if self.__hw_flowmeter_dev is not None and\ fm_state is not None: fm_state.value = self.__hw_flowmeter_dev.get_liters() # If the zone is empty check for leaks. is_empty = self._registers.by_name("envm.is_empty") if self.__hw_flowmeter_dev is not None and\ is_empty is not None and\ is_empty.value: self.__hw_leak_test.run() #endregion #region Private Methods (Power Analyser) def __read_all_parameters(self): black_list = [ "GetRelays", "GetDigitalInputs", "GetAnalogOutputs", "GetAnalogInputs", "SetRelays", "SetAnalogOutputs", ] for parameter in self.__power_analyser.parameters: name = parameter.parameter_name if name in black_list: continue value = self.__read_local_parameter(name) print("{}: {}".format(name, value)) def __read_local_parameter(self, name): value = 0.0 request = self.__power_analyser.generate_request(name) if request is not None: response = self._controller.execute_mb_request( request, self.__power_analyser.uart) if not response.isError(): registers = {} for index in range(request.address, request.address + request.count): registers[index] = response.registers[index - request.address] value = self.__power_analyser.get_parameter_value( name, registers) return value def __read_unipi_mb_master(self): # Get structure data. registers_ids = self.__power_analyser.get_registers_ids() # Get values by the structure. registers_values = self._controller.read_mb_registers(\ self.__uart, \ self.__dev_id, \ registers_ids, \ RegisterType.ReadInputRegisters) # Convert values to human readable. parameters_values = self.__power_analyser.get_parameters_values( registers_values) # Format the floating points. for parameter_value in parameters_values: try: parameters_values[parameter_value] = \ float("{:06.3f}".format(float(parameters_values[parameter_value]))) except ValueError: parameters_values[parameter_value] = float( "{:06.3f}".format(000.0)) return parameters_values def __pa_enabled_cb(self, register): if not register.data_type == "json": return if register.value != {} and self.__power_analyser is None: self.__power_analyser = PowerAnalyserFactory.create( controller=self._controller, name="Zone Power analyser", vendor=register.value['vendor'], model=register.value['model'], options=register.value['options']) if self._controller.vendor == "UniPi": # Load EVOK settings. if os.name == "posix": self.__evok_setting = EvokSettings("/etc/evok.conf") if os.name == "nt": self.__evok_setting = EvokSettings("evok.conf") # Save settings to the EVOK software. if not self.__evok_setting.device_exists("EXTENTION_1"): # Vendor # vendor = register.value['vendor'] # Model model = register.value['model'] # UART uart = register.value['model'] # Unit unit = register.value['options']['unit'] # Add extention 1. extention_1 = { "global_id": unit, "device_name": model, "modbus_uart_port": "/dev/extcomm/0/0", "allow_register_access": True, "address": uart, "scan_frequency": 10, "scan_enabled": True, "baud_rate": 9600, "parity": "N", "stop_bits": 1 } # Add the configuration. self.__evok_setting.add_named_device( extention_1, "EXTENTION_1") self.__evok_setting.save() self.__logger.debug("Enable the Power Analyser.") # Restart the service. if os.name == "posix": EvokSettings.restart() self.__logger.debug("Restart the EVOK service.") elif register.value == {} and self.__power_analyser is not None: self.__power_analyser = None if self._controller.vendor == "UniPi": if self.__evok_setting.device_exists("EXTENTION_1"): # Remove the settings. self.__evok_setting.remove_device("EXTENTION_1") self.__evok_setting.save() self.__logger.debug("Disable the Power Analyser.") # Restart the service. if os.name == "posix": EvokSettings.restart() self.__logger.debug("Restart the EVOK service.") def __pa_demand_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 if register.value < 0.0: GlobalErrorHandler.log_bad_register_value(self.__logger, register) return if self.__demand_timer is not None: self.__demand_timer.expiration_time = register.value def __init_pa(self): pa_enabled = self._registers.by_name(self.key + ".pa.settings") if pa_enabled is not None: pa_enabled.update_handlers = self.__pa_enabled_cb pa_enabled.update() demand_time = self._registers.by_name(self.key + ".pa.demand_time") if demand_time is not None: demand_time.update_handlers = self.__pa_demand_time_cb demand_time.update() def __update_pa(self): if self.__power_analyser is None: return # self.__read_all_parameters() measurement = { "ImportActiveEnergy": 0.0, "ExportActiveEnergy": 0.0, "ImportReactiveEnergy": 0.0, "ExportReactiveEnergy": 0.0, "Phase1Current": 0.0, "Phase2Current": 0.0, "Phase3Current": 0.0, "ts": 0, } if self.__power_analyser.model == "SDM120": if self._controller.vendor == "UniPi": values = self.__read_unipi_mb_master() measurement["ImportActiveEnergy"] = values[ "ImportActiveEnergy"] measurement["ExportActiveEnergy"] = values[ "ExportActiveEnergy"] measurement["ImportReactiveEnergy"] = values[ "ImportReactiveEnergy"] measurement["ExportReactiveEnergy"] = values[ "ExportReactiveEnergy"] measurement["Phase1Current"] = values["Current"] else: measurement[ "ImportActiveEnergy"] = self.__read_local_parameter( "ImportActiveEnergy") measurement[ "ExportActiveEnergy"] = self.__read_local_parameter( "ExportActiveEnergy") measurement[ "ImportReactiveEnergy"] = self.__read_local_parameter( "ImportReactiveEnergy") measurement[ "ExportReactiveEnergy"] = self.__read_local_parameter( "ExportReactiveEnergy") measurement["Phase1Current"] = self.__read_local_parameter( "Current") elif self.__power_analyser.model == "SDM630": if self._controller.vendor == "UniPi": values = self.__read_unipi_mb_master() measurement["ImportActiveEnergy"] = values["TotalImportkWh"] measurement["ExportActiveEnergy"] = values["TotalExportkWh"] measurement["ImportReactiveEnergy"] = values[ "TotalImportkVArh"] measurement["ExportReactiveEnergy"] = values[ "TotalExportkVArh"] measurement["Phase1Current"] = values["Phase1Current"] measurement["Phase2Current"] = values["Phase2Current"] measurement["Phase3Current"] = values["Phase3Current"] else: measurement[ "ImportActiveEnergy"] = self.__read_local_parameter( "TotalImportkWh") measurement[ "ExportActiveEnergy"] = self.__read_local_parameter( "TotalExportkWh") measurement[ "ImportReactiveEnergy"] = self.__read_local_parameter( "TotalImportkVArh") measurement[ "ExportReactiveEnergy"] = self.__read_local_parameter( "TotalExportkVArh") measurement["Phase1Current"] = self.__read_local_parameter( "Phase1Current") measurement["Phase2Current"] = self.__read_local_parameter( "Phase2Current") measurement["Phase3Current"] = self.__read_local_parameter( "Phase3Current") else: self.__logger.error("Unknown power analyser") # Set the time of the measurement. measurement["ts"] = time.time() # Add measurement to the tail. self.__measurements.append(measurement) # This magical number represents seconds for 24 hours. self.__filter_measurements_by_time(self.__measurements, 86400) # Update parameters in the registers. self._registers.write("{}.pa.measurements".format(self.key), json.dumps(self.__measurements)) #endregion #region Public Methods def _init(self): """Initialize the plugin. """ self.__logger = get_logger(__name__) self.__logger.info("Starting up the {}".format(self.name)) self.__demand_timer = Timer(3600) # Init cold water flow meter. self.__init_cw() # Init hot water flow meter. self.__init_hw() # Init power analyser. self.__init_pa() def _update(self): """Update the plugin. """ # Update cold water flow meter. self.__update_cw() # Update hot water flow meter. self.__update_hw() # Check is it time to measure. self.__demand_timer.update() if self.__demand_timer.expired: # Clear the timer. self.__demand_timer.clear() # Update power analyser. self.__update_pa() def _shutdown(self): """Shutting down the plugin. """ self.__logger.info("Shutting down the {}".format(self.name))
class Environment(BasePlugin): """Environment control and provisioning.""" #region Attributes __logger = None """Logger""" __update_timer = None """Update timer.""" __sunpos_enabled_value = False """Sun position local calculation enabled.""" __location_lat = 0.0 """Location latitude. """ __location_lon = 0.0 """Location longitude. """ __location_elv = 0.0 """Location elevation. """ __time_zone = 0 """Location time zone. """ __temperature = 0 """Temperature """ #endregion #region Private Methods (Registers) def __sunpos_enabled_cb(self, register): """Sun position calculation enable flag handler. Args: register (Register): The register. """ # Check data type. if not register.data_type == "bool": GlobalErrorHandler.log_bad_register_value(self.__logger, register) return if self.__sunpos_enabled_value != register.value: self.__sunpos_enabled_value = register.value def __location_lat_cb(self, register): """Get latitude of the building. Args: register (Register): The register. """ # Check data type. if not ((register.data_type == "float") or (register.data_type == "int")): GlobalErrorHandler.log_bad_register_value(self.__logger, register) return if self.__location_lat != register.value: self.__location_lat = register.value def __location_lon_cb(self, register): """Get longitude of the building. Args: register (Register): The register. """ # Check data type. if not ((register.data_type == "float") or (register.data_type == "int")): GlobalErrorHandler.log_bad_register_value(self.__logger, register) return if self.__location_lon != register.value: self.__location_lon = register.value def __location_elv_cb(self, register): """Get elevation of the building. Args: register (Register): The register. """ # Check data type. if not ((register.data_type == "float") or (register.data_type == "int")): GlobalErrorHandler.log_bad_register_value(self.__logger, register) return if self.__location_elv != register.value: self.__location_elv = register.value def __time_zone_cb(self, register): """Get time zone. Args: register (Register): The register. """ # Check data type. if not ((register.data_type == "float") or (register.data_type == "int")): GlobalErrorHandler.log_bad_register_value(self.__logger, register) return if self.__time_zone != register.value: self.__time_zone = register.value def __temperature_cb(self, register): """Get environment temperature. Args: register (Register): The register. """ # Check data type. if not ((register.data_type == "float") or (register.data_type == "int")): GlobalErrorHandler.log_bad_register_value(self.__logger, register) return if self.__temperature != register.value: self.__temperature = register.value def __init_registers(self): # Software sun position enabled. # sunpos_enabled = self._registers.by_name(self.key + ".sunpos.enabled") sunpos_enabled = self._registers.by_name("envm.sunpos.enabled") if sunpos_enabled is not None: sunpos_enabled.update_handlers = self.__sunpos_enabled_cb sunpos_enabled.update() location_lat = self._registers.by_name("envm.building.location.lat") if location_lat is not None: location_lat.update_handlers = self.__location_lat_cb location_lat.update() location_lon = self._registers.by_name("envm.building.location.lon") if location_lon is not None: location_lon.update_handlers = self.__location_lon_cb location_lon.update() location_elv = self._registers.by_name("envm.building.location.elv") if location_elv is not None: location_elv.update_handlers = self.__location_elv_cb location_elv.update() time_zone = self._registers.by_name("envm.building.location.time_zone") if time_zone is not None: time_zone.update_handlers = self.__time_zone_cb time_zone.update() temperature = self._registers.by_name("envm.temp.actual") if temperature is not None: temperature.update_handlers = self.__temperature_cb temperature.update() def __set_sunpos(self): """Set sun position. """ self._registers.write("envm.sun.elevation", self.__elevation) self._registers.write("envm.sun.azimuth", self.__azimuth) #endregion #region Private Methods def __calculate_position(self): """Calculate sun position. """ # https://www.suncalc.org/#/43.0781,25.5955,17/2021.05.07/11:09/1/1 # Latitude of the target. lat = self.__location_lat # Longitude of the target. lon = self.__location_lon # Elevation, in meters. # It is formed by the sum of altitude in [m] + height of the object (building) in [m] elv = self.__location_elv # Temperature, in degrees celsius. temp = self.__temperature # Atmospheric pressure, in millibar. presure = 1013.0 # Difference between earth\'s rotation time (TT) and universal time (UT1). diff_time = 3600 * self.__time_zone # Output in radians instead of degrees. mou = False # Get sun position. time_now = datetime.now() azm, zen, ra, dec, h = sunpos(time_now, lat, lon, elv, temp, presure, diff_time, mou) elv_out = 90 - zen - 2 azm_out = azm # self.__logger.info(f"Azimuth: {azm:.2f}; Elevation: {elv:.2f}") # print(f"SunPos -> Azm: {azm_out:.2f}; Elev: {elv_out:.2f}") # Update sun location. self.__azimuth = azm_out self.__elevation = elv_out #endregion #region Public Methods def _init(self): """Initialize the plugin. """ self.__logger = get_logger(__name__) self.__logger.info("Starting up the {}".format(self.name)) self.__update_timer = Timer(2) self.__init_registers() def _update(self): """Update the plugin. """ self.__update_timer.update() if self.__update_timer.expired: self.__update_timer.clear() if self.__sunpos_enabled_value: self.__calculate_position() self.__set_sunpos() def _shutdown(self): """Shutting down the plugin. """ self.__logger.info("Shutting down the {}".format(self.name))
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 Light(BasePlugin): """Light controller plugin.""" #region Attributes __logger = None """Logger """ __update_timer = None """Update timer. """ __light_sensor = None """Light sensor. """ __v1_output = verbal_const.OFF """Analog voltage output 1. """ __v2_output = verbal_const.OFF """Analog voltage output 2. """ __target_illumination = 50.0 """Target illumination. [Lux] """ __error_gain = 0.001 """Gain of the error. This parameter is the smoothness of the curve. """ __output_limit = 10000 """Illumination force limit. [V] """ __tmp_output = 0 """Temporary output. [V] """ __output = 0 """Main output. [V] """ #endregion #region Private Methods (Registers Interface) def __error_gain_cb(self, register): # Check data type. if not (register.data_type == "float" or register.data_type == "int"): GlobalErrorHandler.log_bad_register_value(self.__logger, register) return if self.__error_gain != register.value: self.__error_gain = register.value def __sensor_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.__light_sensor is None: self.__light_sensor = LightSensorFactory.create( controller=self._controller, name="Room light sensor.", vendor=register.value['vendor'], model=register.value['model'], options=register.value['options']) if self.__light_sensor is not None: self.__light_sensor.init() elif register.value == {} and self.__light_sensor is not None: self.__light_sensor.shutdown() del self.__light_sensor def __v1_output_cb(self, register): # Check data type. if not register.data_type == "str": GlobalErrorHandler.log_bad_register_data_type( self.__logger, register) return self.__v1_output = register.value def __v2_output_cb(self, register): # Check data type. if not register.data_type == "str": GlobalErrorHandler.log_bad_register_data_type( self.__logger, register) return self.__v2_output = register.value def __target_illum_cb(self, register): # Check data type. if not (register.data_type == "float" or register.data_type == "int"): GlobalErrorHandler.log_bad_register_value(self.__logger, register) return if self.__target_illumination != register.value: self.__target_illumination = register.value def __init_registers(self): sensor_enabled = self._registers.by_name(self.key + ".sensor.settings") if sensor_enabled is not None: sensor_enabled.update_handlers = self.__sensor_settings_cb sensor_enabled.update() v1_output = self._registers.by_name(self.key + ".v1.output") if v1_output is not None: v1_output.update_handlers = self.__v1_output_cb v1_output.update() v2_output = self._registers.by_name(self.key + ".v2.output") if v2_output is not None: v2_output.update_handlers = self.__v2_output_cb v2_output.update() error_gain = self._registers.by_name(self.key + ".error_gain") if error_gain is not None: error_gain.update_handlers = self.__error_gain_cb error_gain.update() target_illum = self._registers.by_name(self.key + ".target_illum") if target_illum is not None: target_illum.update_handlers = self.__target_illum_cb target_illum.update() 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 #endregion #region Private Methods (Controller Interface) def __set_voltages(self, voltage_1, voltage_2): """Set the voltage outputs. Parameters ---------- voltage_1 : float Voltage 1. voltage_2 : float Voltage 2. """ value_v1 = voltage_1 if value_v1 > 10: value_v1 = 10 if value_v1 < 0: value_v1 = 0 value_v2 = voltage_2 if value_v2 > 10: value_v2 = 10 if value_v2 < 0: value_v2 = 0 if self._controller.is_valid_gpio(self.__v1_output): self._controller.analog_write(self.__v1_output, value_v1) if self._controller.is_valid_gpio(self.__v2_output): self._controller.analog_write(self.__v2_output, value_v2) #endregion #region Private Methods def __flood_fade(self, setpoint): """Flood fade generator. Args: setpoint (float): Setpoint for the hardware. Returns: list: Output voltages for the two analog outputs. """ # Negative limit. if setpoint < 0: setpoint = 0 # Positive limit. if setpoint > 100: setpoint = 100 voltage_1 = 0 voltage_2 = 0 # The model. if setpoint <= 50: voltage_1 = l_scale(setpoint, [0, 50], [0, 100]) voltage_2 = 0 else: voltage_1 = 100 voltage_2 = l_scale(setpoint, [50, 100], [0, 100]) # Return the voltages. return (voltage_1, voltage_2) def __calculate(self): """ Apply thermal force to the devices. """ current_illumination = 0 target_illumination = 0 delta = 0 error = 0 # If there is no one at the zone, just turn off the lights. is_empty = self.__is_empty() # Scale t target_illumination = l_scale(self.__target_illumination, [0.0, self.__output_limit], [0.0, 100.0]) # Read sensor. if self.__light_sensor is not None: current_illumination = self.__light_sensor.get_value() current_illumination = l_scale(current_illumination, [0.0, self.__output_limit], [0.0, 100.0]) # Limits lower_limit = 0.0 mid_1 = 20.0 mid_2 = 80.0 upper_limit = 100.0 # Check the limits. if lower_limit <= target_illumination <= mid_1: self.__tmp_output = (target_illumination / mid_1) * 20.0 elif mid_1 < target_illumination < mid_2: # Apply the formula. first = current_illumination second = (1 + ((50.0 - target_illumination) / 100.0)) calculated_value = first * second # Calculate the target error. error = calculated_value - current_illumination # Integrate the delta to temporal output. delta = error * self.__error_gain self.__tmp_output += delta elif mid_2 <= target_illumination <= upper_limit: self.__tmp_output = (target_illumination / upper_limit) * 100. # Limitate the output by target value. if self.__tmp_output > abs(self.__output_limit): self.__tmp_output = self.__output_limit # Limitate by absolute maximum and minimum. if self.__tmp_output < 0: self.__tmp_output = 0 if self.__tmp_output > 100: self.__tmp_output = 100 # Apply the output if it is different. self.__output = self.__tmp_output # Convert to volgate. to_voltage_scale = 0.1 # Magic number!!! voltage_1, voltage_2 = self.__flood_fade(self.__output) out_to_v1 = voltage_1 * to_voltage_scale out_to_v2 = voltage_2 * to_voltage_scale # If the zone is empty, turn the lights off. if is_empty: pass # set the voltage. self.__set_voltages(out_to_v1, out_to_v2) self.__logger.debug("TRG {:3.3f}\tINP {:3.3f}\tERR: {:3.3f}\tOUT1: {:3.3f}\tOUT2: {:3.3f}"\ .format(target_illumination, current_illumination, delta, out_to_v1, out_to_v2)) #endregion #region Public Methods def _init(self): """Initialize the plugin. """ self.__logger = get_logger(__name__) self.__logger.info("Starting up the {}".format(self.name)) self.__update_timer = Timer(1) self.__init_registers() self.__set_voltages(0, 0) def _update(self): """Update the plugin. """ # Update sensor data. self.__light_sensor.update() self.__update_timer.update() if self.__update_timer.expired: self.__update_timer.clear() self.__calculate() def _shutdown(self): """Shutting down the plugin. """ self.__logger.info("Shutting down the {}".format(self.name)) self.__set_voltages(0, 0)
class Zone(): """Main zone class""" #region Attributes __logger = None """Logger""" __app_settings = None """Application settings.""" __controller = None """Neuron controller.""" __erp = None """Communication with the ERP.""" __registers = None """Registers""" __plugin_manager = None """Plugin manager.""" __update_rate = 0.5 """Controlling update rate in seconds.""" __update_timer = None """Update timer.""" __erp_service_update_rate = 5 """ERP service update rate in seconds.""" __erp_service_update_timer = None """ERP update timer.""" __erp_state_machine = None """Zone state.""" __stop_flag = False """Time to Stop flag.""" __busy_flag = False """Busy flag.""" __performance_profiler = PerformanceProfiler() """Performance profiler.""" __performance_profiler_timer = None """Performance profiler timer.""" # (Request to stop the queue from MG @ 15.01.2021) # __registers_snapshot = None # """Registers snapshot.""" #endregion #region Private Methods (Registers Interface) def __target_version_cb(self, register: Register): current_version = None target_version = None target_version_reg = register # Check data type of the current version. if not target_version_reg.data_type == "json": GlobalErrorHandler.log_bad_register_data_type(self.__logger, target_version_reg) return # Get target version value. if target_version_reg.value is not None: target_version = target_version_reg.value # Check the target version value. if target_version == None: GlobalErrorHandler.log_bad_register_value(self.__logger, target_version) return # Get current version register. current_version_reg = self.__registers.by_name("sys.software.current_version") # Check data type. if not ((current_version_reg.data_type == "json")): GlobalErrorHandler.log_bad_register_data_type(self.__logger, current_version_reg) return if current_version_reg is not None: current_version = current_version_reg.value # Check the current version value. if current_version == None: GlobalErrorHandler.log_bad_register_value(self.__logger, current_version) return # Update the software. software_update(current_version_reg.value, target_version_reg.value) # Save the current version. self.__app_settings.current_version = target_version_reg.value self.__app_settings.save() def __init_registers(self): """Setup registers source. """ registers_file = "" # Current file path. & Go to file. cwf = os.path.dirname(os.path.abspath(__file__)) registers_file = os.path.join(cwf, "..", "registers.csv") # Load depending of file format. if registers_file.endswith('json'): self.__registers = Registers.from_json(registers_file) elif registers_file.endswith('csv'): self.__registers = Registers.from_csv(registers_file) else: sys.exit(0) target_version = self.__registers.by_name("sys.software.current_version") if target_version is not None: target_version.value = self.__app_settings.current_version target_version = self.__registers.by_name("sys.software.target_version") if target_version is not None: target_version.update_handlers = self.__target_version_cb # TODO: Will we ask for update on every start? #endregion #region Private Methods (PLC) def __evok_cb(self, device): """EVOK callback service handler. Args: device (JSON): GPIOs that was changed. """ # Target inputs, by registers names. names = ["ac.door_closed_1.input", "ac.door_closed_2.input",\ "ac.pir_1.input", "ac.pir_2.input",\ "ac.window_closed_1.input", "ac.window_closed_2.input",\ "monitoring.cw.input", "monitoring.hw.input", "sys.at.input"] inputs = self.__registers.by_names(names) gpio = self.__controller.device_to_uniname(device) for input_reg in inputs: if input_reg is not None: if input_reg.value == gpio: # If register exists, apply value. required_name = input_reg.name.replace("input", "state") if self.__registers is not None: self.__registers.write(required_name, device["value"]) def __init_controller(self): """Initialize the controller. """ # Create PLC. self.__controller = ControllerFactory.create(self.__app_settings.controller) if self.__controller is None: raise ValueError("Controller is not created.") # Print out the vendor and model of the controller. self.__logger.info(self.__controller) if self.__controller.vendor == "unipi": self.__controller.set_webhook(self.__evok_cb) self.__controller.start_web_service() # Create entrance if os.name == "posix": # Load settings evok_settings = EvokSettings("/etc/evok.conf") # Modifie evok_settings.webhook_address = "http://127.0.0.1:8889/api/v1/evok/webhooks ; Put your server endpoint address here (e.g. http://123.123.123.123:/wh )" evok_settings.webhook_enabled = True evok_settings.webhook_device_mask = ["input", "wd"] evok_settings.webhook_complex_events = True # Save evok_settings.save() # Restart the service to accept the settings. EvokSettings.restart() #endregion #region Private Methods (ERP) def __on_erp_state_change_cb(self, machine): """React on ERP state changes. Args: machine (StateMachine): State machine state. """ self.__logger.info("ERP state: {}".format(machine.get_state())) def __erp_set_registers(self, data): """ERP set registers values. Args: data (dict): Registers names and values. """ # (Request to have WEB API for work with registers. MG @ 15.01.2021) result = {} registers = {} if data is not None: registers = data["registers"] for register_name in registers: register = self.__registers.by_name(register_name) if registers is not None: # Apply the changes. register.value = registers[register_name] # In th response add what is going on. result[register.name] = register.value return result def __erp_get_registers(self, data): """ERP get registers values. Args: registers (dict): Names of registers. Returns: dict: Dictionary of registers values and their names. """ # (Request to have WEB API for work with registers. MG @ 15.01.2021) result = {} registers = {} if data is not None: registers = data["registers"] for register_name in registers: register = self.__registers.by_name(register_name) if register is not None: result[register.name] = register.value return result def __init_erp(self): """Setup the ERP. """ # Take ERP info from settings. erp_host = self.__app_settings.get_erp_service["host"] erp_timeout = self.__app_settings.get_erp_service["timeout"] # Create ERP. self.__erp = bgERP(host=erp_host, timeout=erp_timeout) # Set callbacks. self.__erp.set_registers_cb( get_cb=self.__erp_get_registers, set_cb=self.__erp_set_registers) # Set the ERP update timer. self.__erp_service_update_timer = Timer(self.__erp_service_update_rate) # Set zone state machine. self.__erp_state_machine = StateMachine() self.__erp_state_machine.on_change(self.__on_erp_state_change_cb) self.__erp_state_machine.set_state(ERPState.NONE) def __erp_login(self): """Login to bgERP. """ login_state = self.__erp.login( serial_number=self.__controller.serial_number, model=self.__controller.model, version=self.__controller.version, config_time=self.__app_settings.get_erp_service["config_time"], bgerp_id=self.__app_settings.get_erp_service["erp_id"]) if login_state: # Rewrite the ERP service ID. if self.__app_settings.get_erp_service["erp_id"] != self.__erp.erp_id: self.__app_settings.get_erp_service["erp_id"] = self.__erp.erp_id self.__app_settings.save() self.__erp_state_machine.set_state(ERPState.Update) else: self.__erp_state_machine.set_state(ERPState.Login) def __erp_update(self): """Sync registers. """ # Update periodically bgERP. self.__erp_service_update_timer.update() if self.__erp_service_update_timer.expired: self.__erp_service_update_timer.clear() ztm_regs = self.__registers.by_scope(Scope.Device) ztm_regs = ztm_regs.new_then(60) ztm_regs_dict = ztm_regs.to_dict() update_state = self.__erp.sync(ztm_regs_dict) if update_state is not None: # is not None if self.__registers is not None: self.__registers.update(update_state) # Clear the last atendies. (Eml6287) self.__registers.write("ac.last_update_attendees", str([])) # (Eml6287) # (Request to stop the queue from MG @ 15.01.2021) # not_send_len = self.__registers_snapshot.qsize() # if not_send_len > 0: # Get from the queue. # snapshot = self.__registers_snapshot.get() # Send the firs from the queue. # self.__erp.sync(snapshot) else: GlobalErrorHandler.log_no_connection_erp(self.__logger) self.__erp_state_machine.set_state(ERPState.Login) # # (Request to stop the queue from MG @ 15.01.2021) # # Create absolute copy of the object. # reg_copy = self.__registers.by_scope(Scope.Device).to_dict().copy() # # Put the copy to the queue. # self.__registers_snapshot.put(reg_copy) def __update_erp(self): """Update ERP. """ if self.__erp_state_machine.is_state(ERPState.Login): # Login room. self.__erp_login() elif self.__erp_state_machine.is_state(ERPState.Update): # Run the process of the room. self.__erp_update() elif self.__erp_state_machine.is_state(ERPState.NONE): self.__erp_state_machine.set_state(ERPState.Login) #endregion #region Private Methods (Runtime) def __init_runtime(self): # Update timer. self.__update_timer = Timer(self.__update_rate) # Update with offset based on the serial number of the device. time_offset = 0 if self.__controller.serial_number is not None and self.__controller.serial_number.isdigit(): time_offset = int(self.__controller.serial_number) # Preset the time. self.__update_timer.expiration_time += (time_offset / 1000) #endregion #region Private Methods (Performance Profiler) def __init_performance_profiler(self): """Set the performance profiler. """ self.__performance_profiler.enable_mem_profile = True self.__performance_profiler.enable_time_profile = True self.__performance_profiler.on_time_change(self.__on_time_change) self.__performance_profiler.on_memory_change(self.__on_memory_change) # Setup the performance profiler timer. (60) 10 is for tests. self.__performance_profiler_timer = Timer(10) def __on_time_change(self, passed_time): """On consumed time change. Args: passed_time (int): Consumed runtime. """ if self.__registers is not None: self.__registers.write("sys.time.usage", float("{:10.3f}".format(passed_time))) def __on_memory_change(self, current, peak): """On RAM memory change. Args: current (int): Current RAM. peak (int): Peak RAM. """ if self.__registers is not None: self.__registers.write("sys.ram.current", float("{:10.4f}".format(current))) self.__registers.write("sys.ram.peak", float("{:10.4f}".format(peak))) # print(f"Current memory usage is {current / 10**3}kB; Peak was {peak / 10**3}kB") @__performance_profiler.profile def __update(self): """Update the zone. """ # Create log if if it does not. crate_log_file() # Update the neuron. state = self.__controller.update() if not state: self.__logger.error("PLC service should be restarted.") GlobalErrorHandler.log_no_connection_plc(self.__logger) return # Give plugins runtime. self.__plugin_manager.update() self.__update_erp() #endregion #region Public Methods def init(self): """Initialize the process. """ try: # Application settings. self.__app_settings = ApplicationSettings.get_instance() # Create logger. self.__logger = get_logger(__name__) # Create registers. self.__init_registers() # Create PLC. self.__init_controller() # Create the plugin manager. self.__plugin_manager = PluginsManager(self.__registers, self.__controller) # Setup the ERP. self.__init_erp() # Initialize the performance profiler. self.__init_performance_profiler() # Initialize the runtime. self.__init_runtime() # # (Request to stop the queue from MG @ 15.01.2021) # self.__registers_snapshot = queue.Queue() except Exception: self.__logger.error(traceback.format_exc()) sys.exit(0) def run(self): """Run the zone. """ self.__logger.info("Starting up the Zone") while not self.__stop_flag: # Update process timers. self.__update_timer.update() self.__performance_profiler_timer.update() # If time has come for execution then run it once and clear the timer. if self.__update_timer.expired: self.__update_timer.clear() # If the busy flag is raise pass the update cycle. if self.__busy_flag: pass self.__busy_flag = True try: # If the time has come for profiling. # Enable the flag and tke profile. if self.__performance_profiler_timer.expired: self.__performance_profiler_timer.clear() self.__performance_profiler.enable = True # Update the application. self.__update() # If the performance profile is runing stop it after the cycle. if self.__performance_profiler.enable: self.__performance_profiler.enable = False # Log the exception without to close the application. except Exception: self.__logger.error(traceback.format_exc()) self.__busy_flag = False # Wait until the main teared ends. while self.__busy_flag == True: pass self.__logger.info("Shutting down the Zone") # Shutdown the plugins. self.__plugin_manager.shutdown() def shutdown(self): """Shutdown the process. """ self.__stop_flag = True
class LeakTest: """Leak test class.""" #region Attributes __test_state = None """Test state machine.""" __check_timer = None """Leak test timer.""" __flowmeter_dev = None """Flow meter device.""" __result_cb = None """Callback result device.""" __first_measurement = 0 """First measurement of loop flow meter.""" __second_measurement = 0 """Second measurement of loop flow meter.""" #endregion #region Constructor / Destructor def __init__(self, flowmeter, test_time): self.__flowmeter_dev = flowmeter self.__test_state = StateMachine(TestState.TakeFirstMeasurement) self.__check_timer = Timer(test_time) def __del__(self): if self.__check_timer is not None: del self.__check_timer if self.__test_state is not None: del self.__test_state #endregion #region Public Methods def on_result(self, callback): """On result registration.""" if callback is None: return self.__result_cb = callback def run(self): """Run the test.""" self.__check_timer.update() if self.__check_timer.expired: self.__check_timer.clear() if self.__test_state.is_state(TestState.WaitForLeak): if self.__test_state.was(TestState.TakeFirstMeasurement): self.__test_state.set_state( TestState.TakeSecondMeasurement) elif self.__test_state.was(TestState.TakeSecondMeasurement): self.__test_state.set_state(TestState.TakeFirstMeasurement) elif self.__test_state.is_state(TestState.TakeFirstMeasurement): self.__first_measurement = self.__flowmeter_dev.get_liters() self.__test_state.set_state(TestState.WaitForLeak) elif self.__test_state.is_state(TestState.TakeSecondMeasurement): self.__second_measurement = self.__flowmeter_dev.get_liters() self.__test_state.set_state(TestState.WaitForLeak) leak_liters = abs(self.__second_measurement - self.__first_measurement) if self.__result_cb is not None: self.__result_cb(leak_liters)
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)
class AirChambers(BasePlugin): """Air chambers controller.""" #region Attributes __logger = None """Logger""" __update_timer = None """Update timer.""" #endregion #region Attributes __logger = None """Logger""" __update_timer = None """Update timer.""" #endregion #region Private Methods (Registers Interface) # def __visual_device_settings_cb(self, register: Register): # self.__logger.debug("Init the visual device.") def __init_registers(self): # visual_device_settings = self._registers.by_name("{}.device.visual.settings".format(self.key)) # if visual_device_settings is not None: # visual_device_settings.update_handlers = self.__visual_device_settings_cb # visual_device_settings.update() pass #endregion #region Private Methods def __do_job(self): """Do the JOB method.""" self.__logger.info("Do some job.") #endregion #region Protected Methods def _init(self): """Initialize the plugin. """ self.__logger = get_logger(__name__) self.__logger.info("Starting up the {}".format(self.name)) self.__update_timer = Timer(1) self.__init_registers() # TODO: Check is it necessary to create a air chamber abstraction. Yes it is necessary. # TODO: Create tcp modbus master. def _update(self): """Update the plugin. """ # Update the timer. self.__update_timer.update() if self.__update_timer.expired: self.__update_timer.clear() self.__do_job() # TODO: Pull the data from the chambers. def _shutdown(self): """Shutting down the plugin. """ self.__logger.info("Shutting down the {}".format(self.name))