class Directories(PathContainer): logging: Path = ConfigEntry(Path('log'), description='Folder where the logs will be written to') rules: Path = ConfigEntry(Path('rules'), description='Folder from which the rule files will be loaded') param: Path = ConfigEntry(Path('param'), description='Folder from which the parameter files will be loaded') config: Path = ConfigEntry(Path('config'), description='Folder from which configuration files will be loaded') lib: Path = ConfigEntry(Path('lib'), description='Folder where additional libraries can be placed') def on_all_values_set(self): try: # create folder structure if it does not exist if not self.rules.is_dir(): self.rules.mkdir() if not self.logging.is_dir(): self.logging.mkdir() if not self.config.is_dir(): log.info(f'Manual thing configuration disabled! Folder {self.config} does not exist!') # add path for libraries if self.lib.is_dir(): lib_path = str(self.lib) if lib_path not in sys.path: sys.path.insert(0, lib_path) log.debug(f'Added library folder "{lib_path}" to system path') except Exception as e: log.error(e) print(e)
class Logging(ConfigContainer): level: str = ConfigEntry(default='INFO', description='Verbosity level for the logfile', validator=log_lvl_validator) file: Path = ConfigEntry( default_factory=lambda: str(CONFIG._path.with_name('sml2mqtt.log')), description='Path to logfile')
class General(ConfigContainer): listen_only: bool = ConfigEntry( False, description='If True HABApp will not change anything on the openHAB instance.' ) wait_for_openhab: bool = ConfigEntry( True, description='If True HABApp will wait for items from the openHAB instance before loading any rules on startup' )
def test_path_objects(): c = ConfigEntry(required=True, default=Path('asdf')) c.set_type_hint('test', Path) validator = c.set_validator({}, DEFAULT_CONFIGURATION) # yaml doesnt provide path -> convert to str ret = voluptuous.Schema(validator)({'test': '/my/path'}) assert ret == {'test': Path('/my/path')}
def test_default_value_skip(): CFG_LOWER.create_optional_keys = False ret = {} ConfigEntry(required=False, default='skip', key_name='key_skip').set_default(ret, CFG_LOWER) ConfigEntry(required=True, default='set', key_name='key_set').set_default(ret, CFG_LOWER) assert ret == {'key_set': 'set'}
def test_dict_validator(): c = ConfigEntry(required=True, default_factory=lambda: {'test_key': 'test_val'}) c.set_type_hint('test', typing.Dict[str, str]) validator = c.set_validator({}, DEFAULT_CONFIGURATION) ret = voluptuous.Schema(validator)({}) assert ret == {'test': {'test_key': 'test_val'}}
class Ping(ConfigContainer): enabled: bool = ConfigEntry( True, description= 'If enabled the configured item will show how long it takes to send ' 'an update from HABApp and get the updated value back from openhab' 'in milliseconds') item: str = ConfigEntry('HABApp_Ping', description='Name of the item') interval: int = ConfigEntry(10, description='Seconds between two pings')
def test_list_validator(): c = ConfigEntry(required=True, default_factory=lambda: ['test']) c.set_type_hint('test', typing.List[str]) validator = c.set_validator({}, DEFAULT_CONFIGURATION) assert {'test': [str]} == validator ret = voluptuous.Schema(validator)({}) assert ret == {'test': ['test']}
def test_default_value_create(): CFG_LOWER.create_optional_keys = True ret = {} ConfigEntry(required=False, default=5, key_name='test_int').set_default(ret, CFG_LOWER) assert ret == {'test_int': 5} ConfigEntry(required=False, default='TestString', key_name='test_str').set_default(ret, CFG_LOWER) assert ret == {'test_int': 5, 'test_str': 'TestString'}
def test_required(): c = ConfigEntry(required=True, key_name='test') c.set_type_hint('test', int) validator = c.set_validator({}, CFG_LOWER) with pytest.raises(voluptuous.MultipleInvalid): voluptuous.Schema(validator)({}) voluptuous.Schema(validator)({'test': 5}) c = ConfigEntry(required=False, key_name='test') c.set_type_hint('test', str) validator = c.set_validator({}, CFG_LOWER) voluptuous.Schema(validator)({}) voluptuous.Schema(validator)({'test': 'my_str'})
class Directories(PathContainer): logging: Path = ConfigEntry( get_log_folder(Path('log')), description='Folder where the logs will be written to') rules: Path = ConfigEntry( Path('rules'), description='Folder from which the rule files will be loaded') param: Path = ConfigEntry( Path('params'), description='Folder from which the parameter files will be loaded') config: Path = ConfigEntry( Path('config'), description='Folder from which configuration files ' '(e.g. for textual thing configuration) will be loaded') lib: Path = ConfigEntry( Path('lib'), description='Folder where additional libraries can be placed') def on_all_values_set(self): # Configuration folder of HABApp can not be one of the configured folders for name, path in { attr: getattr(self, attr) for attr in ('rules', 'param', 'config') }.items(): if path == self.parent_folder: msg = f'Path for {name} can not be the same as the path for the HABApp config! ({path})' log.error(msg) sys.exit(msg) try: # create folder structure if it does not exist if not self.rules.is_dir(): self.rules.mkdir() if not self.logging.is_dir(): self.logging.mkdir() if not self.config.is_dir(): log.info( f'Manual thing configuration disabled! Folder {self.config} does not exist!' ) # add path for libraries if self.lib.is_dir(): lib_path = str(self.lib) if lib_path not in sys.path: sys.path.insert(0, lib_path) log.debug( f'Added library folder "{lib_path}" to system path') except Exception as e: log.error(e) print(e)
class Location(ConfigContainer): latitude: float = ConfigEntry(default=0.0, validator=voluptuous.Any(float, int)) longitude: float = ConfigEntry(default=0.0, validator=voluptuous.Any(float, int)) elevation: float = ConfigEntry(default=0.0, validator=voluptuous.Any(float, int)) def __init__(self): super().__init__() def on_all_values_set(self): log.debug(f'Local Timezone: {eascheduler.const.local_tz}') eascheduler.set_location(self.latitude, self.longitude, self.elevation)
def test_description(): data = ruamel.yaml.comments.CommentedMap() ConfigEntry(required=True, default=5, key_name='key_no_comment').set_default(data, CFG_LOWER) ConfigEntry(required=True, default=5, key_name='key_comment', description='Description').set_default(data, CFG_LOWER) tmp = io.StringIO() ruamel.yaml.YAML().dump(data, tmp) output = tmp.getvalue() assert output == 'key_no_comment: 5\nkey_comment: 5 # Description\n'
class General(ConfigContainer): max_wait: int = ConfigEntry( default=120, key_name='max wait', validator=Range(min=2), description= 'Time in seconds sml2mqtt waits for a value change until the value gets republished' )
class Topics(ConfigContainer): base_topic: str = ConfigEntry( 'sml2mqtt', key_name='base topic', description='Topic that will prefix all topics') last_will: str = ConfigEntry('status', key_name='last will', description='Last will topic') alias: Dict[str, str] = ConfigEntry( default_factory=lambda: {'0100010800ff': 'total_energy'}, validator={str: str}, description='These aliases are replaced in the mqtt topics') def on_all_values_set(self): self.alias = {k: v.replace(' ', '_') for k, v in self.alias.items()} def get_topic(self, *args) -> str: args = [self.alias.get(a, a) for a in args] topic = self.base_topic + '/' + '/'.join(args) return topic.replace('//', '/')
class Connection(ConfigContainer): client_id: str = 'HABApp' host: str = '' port: int = 8883 user: str = '' password: str = '' tls: bool = True tls_ca_cert: str = ConfigEntry( default='', description='Path to a CA certificate that will be treated as trusted') tls_insecure: bool = False
def test_default_validator(): c = ConfigEntry(required=True, default=5) c.set_type_hint('test', int) validator = c.set_validator({}, DEFAULT_CONFIGURATION) ret = voluptuous.Schema(validator)({}) assert ret == {'test': 5} ret = voluptuous.Schema(validator)({'test': 7}) assert ret == {'test': 7} c = ConfigEntry(required=True, default='asdf') c.set_type_hint('test', str) validator = c.set_validator({}, DEFAULT_CONFIGURATION) ret = voluptuous.Schema(validator)({}) assert ret == {'test': 'asdf'} ret = voluptuous.Schema(validator)({'test': 'ASDF'}) assert ret == {'test': 'ASDF'}
class Location(ConfigContainer): latitude: float = ConfigEntry(default=0.0, validator=voluptuous.Any(float, int)) longitude: float = ConfigEntry(default=0.0, validator=voluptuous.Any(float, int)) elevation: float = ConfigEntry(default=0.0, validator=voluptuous.Any(float, int)) def __init__(self): super().__init__() self.astral: _astral.Location = None def on_all_values_set(self): tz = tzlocal.get_localzone() tz_name = str(tz) log.debug(f'Local Timezone: {tz_name}') self.astral = _astral.Location() self.astral.name = 'HABApp' self.astral.latitude = self.latitude self.astral.longitude = self.longitude self.astral.elevation = self.elevation self.astral.timezone = tz_name
class Location(ConfigContainer): latitude: float = ConfigEntry(default=0.0, validator=voluptuous.Any(float, int)) longitude: float = ConfigEntry(default=0.0, validator=voluptuous.Any(float, int)) elevation: float = ConfigEntry(default=0.0, validator=voluptuous.Any(float, int)) def __init__(self): super().__init__() self._astral_location: _astral.LocationInfo self.astral_observer: _astral.Observer def on_all_values_set(self): tz = tzlocal.get_localzone() tz_name = str(tz) log.debug(f'Local Timezone: {tz_name}') # unsure why we need the location in 2.1 self._astral_location = _astral.LocationInfo(name='HABApp', ) self._astral_location.latitude = self.latitude self._astral_location.longitude = self.longitude self._astral_location.timezone = tz_name self.astral_observer = self._astral_location.observer self.astral_observer.elevation = self.elevation
class SmlMqttConfig(ConfigFile): mqtt = Mqtt() log = Logging() general = General() devices: List[DeviceConfig] = ConfigEntry( default_factory=lambda: [{ 'device': 'COM1', 'timeout': 3, 'skip': ['value ids that will', 'not be reported'] }, { 'device': '/dev/ttyS0', 'timeout': 3 }], description='Configuration of the sml devices', validator=device_validator)
class SUB_CONTAINER(ConfigContainer): SUB_INT: int = 5 SUB_FLOAT: float = 5.0 SUB_FLOAT_COMMENT: float = 5.5 SUB_MUTABLE_LIST: typing.List[str] = ConfigEntry()
class asdf(ConfigContainer): my_int: int = 5 my_float: float = 3.3 my_float_comment: float = ConfigEntry(default=5.5, description='testest')
class Test(ConfigFile): a = asdf() top_level_str: str = 'adsf' top_level_entry: float = ConfigEntry(default=5.5, description=' testest')
class Subscribe(ConfigContainer): qos: int = ConfigEntry(default=0, description='Default QoS for subscribing') topics: typing.List[typing.Union[str, int]] = ConfigEntry( default_factory=lambda: list(('#', 0)), validator=MqttTopicValidator)
class Publish(ConfigContainer): qos: int = ConfigEntry(default=0, description='Default QoS when publishing values') retain: bool = ConfigEntry( default=False, description='Default retain flag when publishing values')
class General(ConfigContainer): listen_only: bool = ConfigEntry( False, description='If True HABApp will not publish any value to the broker')