Пример #1
0
def read_config():
    """Open the YAML configuration file and check the contents"""
    try:
        yaml.FullLoader.add_constructor('!secret', secret_yaml)
        yaml_file = os.path.join(os.path.dirname(os.path.abspath(__file__)), CONFIG_YAML)
        config = config_from_yaml(data=yaml_file, read_from_file=True)
        if config:
            config = check_config(config)
        return config
    except ConfigError as e:
        raise FailedInitialization(f"ConfigError exception: {e}")
    except FailedInitialization:
        raise
    except Exception as e:
        error_message = buildYAMLExceptionString(exception=e, file=yaml_file)
        raise FailedInitialization(f"Unexpected exception: {error_message}")
Пример #2
0
def check_config(mqtt):
    """Check that the needed YAML options exist."""
    errors = False
    required = {
        'enable': bool,
        'client': str,
        'ip': str,
        'port': int,
        'username': str,
        'password': str
    }
    options = dict(mqtt)
    for key in required:
        if key not in options.keys():
            _LOGGER.error(
                f"Missing required 'mqtt' option in YAML file: '{key}'")
            errors = True
        else:
            v = options.get(key, None)
            if not isinstance(v, required.get(key)):
                _LOGGER.error(
                    f"Expected type '{required.get(key).__name__}' for option 'mqtt.{key}'"
                )
                errors = True
    if errors:
        raise FailedInitialization(
            Exception("Errors detected in 'mqtt' YAML options"))
    return options
Пример #3
0
def retrieve_options(config, key, option_list) -> dict:
    """Retrieve requested options."""
    if key not in config.keys():
        return {}

    errors = False
    options = dict(config[key])
    for option, value in option_list.items():
        required = value.get('required', None)
        type = value.get('type', None)
        if required:
            if option not in options.keys():
                _LOGGER.error(
                    f"Missing required option in YAML file: '{option}'")
                errors = True
            else:
                v = options.get(option, None)
                if not isinstance(v, type):
                    _LOGGER.error(
                        f"Expected type '{type}' for option '{option}'")
                    errors = True
    if errors:
        raise FailedInitialization(
            f"One or more errors detected in '{key}' YAML options")
    return options
Пример #4
0
def check_unsupported(yaml, required, path=''):
    try:
        passed = True
        if not yaml:
            raise FailedInitialization("YAML file is corrupt or truncated, nothing left to parse")
        if isinstance(yaml, list):
            for index, element in enumerate(yaml):
                for yk in element.keys():
                    listpath = f"{path}.{yk}[{index}]"

                    yamlValue = dict(element).get(yk, None)
                    for rk in required:
                        supportedSubkeys = rk.get(yk, None)
                        if supportedSubkeys:
                            break
                    if not supportedSubkeys:
                        _LOGGER.info(f"'{listpath}' option is unsupported")
                        return

                    subkeyList = supportedSubkeys.get('keys', None)
                    if subkeyList:
                        passed = check_unsupported(yamlValue, subkeyList, listpath) and passed
        elif isinstance(yaml, dict) or isinstance(yaml, Configuration):
            for yk in yaml.keys():
                currentpath = path + yk if path == '' else path + '.' + yk

                yamlValue = dict(yaml).get(yk, None)
                for rk in required:
                    supportedSubkeys = rk.get(yk, None)
                    if supportedSubkeys:
                        break
                if not supportedSubkeys:
                    _LOGGER.info(f"'{currentpath}' option is unsupported")
                    return

                subkeyList = supportedSubkeys.get('keys', None)
                if subkeyList:
                    passed = check_unsupported(yamlValue, subkeyList, currentpath) and passed
        else:
            raise FailedInitialization('Unexpected YAML checking error')
    except FailedInitialization:
        raise
    except Exception as e:
        raise FailedInitialization(f"Unexpected exception: {e}")
    return passed
Пример #5
0
def check_config(config):
    """Check that the important options are present and unknown options aren't."""

    required_keys = [
        {
            'cs_esphome': {'required': True, 'keys': [
                {'circuitsetup': {'required': True, 'keys': [
                    {'url': {'required': True, 'keys': [], 'type': str}},
                    {'port': {'required': False, 'keys': [], 'type': int}},
                    {'password': {'required': False, 'keys': [], 'type': str}},
                ]}},
                {'influxdb2': {'required': False, 'keys': [
                    {'org': {'required': True, 'keys': [], 'type': str}},
                    {'url': {'required': True, 'keys': [], 'type': str}},
                    {'bucket': {'required': True, 'keys': [], 'type': str}},
                    {'token': {'required': True, 'keys': [], 'type': str}},
                    {'pruning': {'required': True, 'keys': [
                        {'task': {'required': True, 'keys': [
                            {'name': {'required': True, 'keys': [], 'type': str}},
                            {'predicate': {'required': True, 'keys': [], 'type': str}},
                            {'keep_last': {'required': True, 'keys': [], 'type': int}},
                        ]}},
                    ]}},
                ]}},
                {'debug': {'required': False, 'keys': [
                    {'create_bucket': {'required': False, 'keys': [], 'type': bool}},
                    {'delete_bucket': {'required': False, 'keys': [], 'type': bool}},
                    {'fill_data': {'required': False, 'keys': [], 'type': bool}},
                ]}},
                {'sensors': {'required': True, 'keys': [
                    {'sensor': {'required': True, 'keys': [
                        {'enable': {'required': False, 'keys': [], 'type': bool}},
                        {'sensor_name': {'required': True, 'keys': [], 'type': str}},
                        {'display_name': {'required': True, 'keys': [], 'type': str}},
                        {'measurement': {'required': True, 'keys': [], 'type': str}},
                        {'device': {'required': True, 'keys': [], 'type': str}},
                        {'location': {'required': True, 'keys': [], 'type': str}},
                        {'integrate': {'required': False, 'keys': [], 'type': bool}},
                    ]}},
                ]}},
                {'settings': {'required': False, 'keys': [
                    {'sampling': {'required': False, 'keys': [
                        {'delta_wh': {'required': False, 'keys': [], 'type': int}},
                        {'integrations': {'required': False, 'keys': [
                            {'today': {'required': False, 'keys': [], 'type': int}},
                            {'month': {'required': False, 'keys': [], 'type': int}},
                            {'year': {'required': False, 'keys': [], 'type': int}},
                        ]}},
                        {'locations': {'required': False, 'keys': [
                            {'today': {'required': False, 'keys': [], 'type': int}},
                            {'month': {'required': False, 'keys': [], 'type': int}},
                            {'year': {'required': False, 'keys': [], 'type': int}},
                        ]}},
                    ]}},
                    {'watchdog': {'required': False, 'keys': [], 'type': int}},
                ]}},
            ]},
        },
    ]

    try:
        result = check_required_keys(dict(config), required_keys)
        check_unsupported(dict(config), required_keys)
    except FailedInitialization:
        raise
    except Exception as e:
        raise FailedInitialization(f"Unexpected exception: {e}")
    return config if result else None
Пример #6
0
def check_required_keys(yaml, required, path='') -> bool:
    passed = True

    for keywords in required:
        for rk, rv in keywords.items():
            currentpath = path + rk if path == '' else path + '.' + rk

            requiredKey = rv.get('required')
            requiredSubkeys = rv.get('keys')
            keyType = rv.get('type', None)
            typeStr = '' if not keyType else f" (type is '{keyType.__name__}')"

            if not yaml:
                raise FailedInitialization(f"YAML file is corrupt or truncated, expecting to find '{rk}' and found nothing")

            if isinstance(yaml, list):
                for index, element in enumerate(yaml):
                    path = f"{currentpath}[{index}]"

                    yamlKeys = element.keys()
                    if requiredKey:
                        if rk not in yamlKeys:
                            _LOGGER.error(f"'{currentpath}' is required for operation {typeStr}")
                            passed = False
                            continue

                    yamlValue = dict(element).get(rk, None)
                    if yamlValue is None:
                        return passed

                    if rk in yamlKeys and keyType and not isinstance(yamlValue, keyType):
                        _LOGGER.error(f"'{currentpath}' should be type '{keyType.__name__}'")
                        passed = False

                    if isinstance(requiredSubkeys, list):
                        if len(requiredSubkeys):
                            passed = check_required_keys(yamlValue, requiredSubkeys, path) and passed
                    else:
                        raise FailedInitialization(Exception('Unexpected YAML checking error'))
            elif isinstance(yaml, dict) or isinstance(yaml, Configuration):
                yamlKeys = yaml.keys()
                if requiredKey:
                    if rk not in yamlKeys:
                        _LOGGER.error(f"'{currentpath}' is required for operation {typeStr}")
                        passed = False
                        continue

                yamlValue = dict(yaml).get(rk, None)
                if yamlValue is None:
                    return passed

                if rk in yamlKeys and keyType and not isinstance(yamlValue, keyType):
                    _LOGGER.error(f"'{currentpath}' should be type '{keyType.__name__}'")
                    passed = False

                if isinstance(requiredSubkeys, list):
                    if len(requiredSubkeys):
                        passed = check_required_keys(yamlValue, requiredSubkeys, currentpath) and passed
                else:
                    raise FailedInitialization('Unexpected YAML checking error')
            else:
                raise FailedInitialization('Unexpected YAML checking error')
    return passed
Пример #7
0
    def start(self):
        """Initialize the InfluxDB client."""
        try:
            influxdb_options = retrieve_options(self._config, 'influxdb2',
                                                _INFLUXDB2_OPTIONS)
            debug_options = retrieve_options(self._config, 'debug',
                                             _DEBUG_OPTIONS)
        except FailedInitialization as e:
            _LOGGER.error(f"{e}")
            return False

        if len(influxdb_options.keys()) == 0:
            raise FailedInitialization("missing 'influxdb2' options")

        result = False
        try:
            self._bucket = influxdb_options.get('bucket', None)
            self._url = influxdb_options.get('url', None)
            self._token = influxdb_options.get('token', None)
            self._org = influxdb_options.get('org', None)
            self._client = InfluxDBClient(url=self._url,
                                          token=self._token,
                                          org=self._org,
                                          enable_gzip=True)
            if not self._client:
                raise FailedInitialization(
                    f"failed to get InfluxDBClient from '{self._url}' (check url, token, and/or organization)"
                )
            self._write_api = self._client.write_api(write_options=SYNCHRONOUS)
            self._query_api = self._client.query_api()
            self._delete_api = self._client.delete_api()
            self._tasks_api = self._client.tasks_api()
            self._organizations_api = self._client.organizations_api()

            cs_esphome_debug = os.getenv(_DEBUG_ENV_VAR,
                                         'False').lower() in ('true', '1', 't')
            try:
                if cs_esphome_debug and debug_options.get(
                        'delete_bucket', False):
                    self.delete_bucket()
                    _LOGGER.info(
                        f"Deleted bucket '{self._bucket}' at '{self._url}'")
            except InfluxDBBucketError as e:
                raise FailedInitialization(f"{e}")

            try:
                if not self.connect_bucket(
                        cs_esphome_debug
                        and debug_options.get('create_bucket', False)):
                    raise FailedInitialization(
                        f"Unable to access (or create) bucket '{self._bucket}' at '{self._url}'"
                    )
            except InfluxDBBucketError as e:
                raise FailedInitialization(f"{e}")

            _LOGGER.info(
                f"Connected to InfluxDB: '{self._url}', bucket '{self._bucket}'"
            )
            result = True

        except FailedInitialization as e:
            _LOGGER.error(f" client {e}")
            self._client = None
        except NewConnectionError:
            _LOGGER.error(
                f"InfluxDB client unable to connect to host at {self._url}")
        except ApiException as e:
            _LOGGER.error(
                f"InfluxDB client unable to access bucket '{self._bucket}' at {self._url}: {e.reason}"
            )
        except Exception as e:
            _LOGGER.error(f"Unexpected exception: {e}")
        finally:
            return result
Пример #8
0
def check_config(config):
    """Check that the important options are present and unknown options aren't."""

    required_keys = [
        {
            'multisma2': {
                'required':
                True,
                'keys': [
                    {
                        'site': {
                            'required':
                            True,
                            'keys': [
                                {
                                    'name': {
                                        'required': True,
                                        'keys': [],
                                        'type': str
                                    }
                                },
                                {
                                    'region': {
                                        'required': True,
                                        'keys': [],
                                        'type': str
                                    }
                                },
                                {
                                    'tz': {
                                        'required': True,
                                        'keys': [],
                                        'type': str
                                    }
                                },
                                {
                                    'latitude': {
                                        'required': True,
                                        'keys': [],
                                        'type': float
                                    }
                                },
                                {
                                    'longitude': {
                                        'required': True,
                                        'keys': [],
                                        'type': float
                                    }
                                },
                                {
                                    'elevation': {
                                        'required': True,
                                        'keys': [],
                                        'type': float
                                    }
                                },
                                {
                                    'co2_avoided': {
                                        'required': True,
                                        'keys': [],
                                        'type': float
                                    }
                                },
                            ]
                        }
                    },
                    {
                        'solar_properties': {
                            'required':
                            True,
                            'keys': [
                                {
                                    'azimuth': {
                                        'required': True,
                                        'keys': [],
                                        'type': float
                                    }
                                },
                                {
                                    'tilt': {
                                        'required': True,
                                        'keys': [],
                                        'type': float
                                    }
                                },
                                {
                                    'area': {
                                        'required': True,
                                        'keys': [],
                                        'type': float
                                    }
                                },
                                {
                                    'efficiency': {
                                        'required': True,
                                        'keys': [],
                                        'type': float
                                    }
                                },
                                {
                                    'rho': {
                                        'required': True,
                                        'keys': [],
                                        'type': float
                                    }
                                },
                            ]
                        }
                    },
                    {
                        'influxdb2': {
                            'required':
                            False,
                            'keys': [
                                {
                                    'enable': {
                                        'required': True,
                                        'keys': [],
                                        'type': bool
                                    }
                                },
                                {
                                    'org': {
                                        'required': True,
                                        'keys': [],
                                        'type': str
                                    }
                                },
                                {
                                    'url': {
                                        'required': True,
                                        'keys': [],
                                        'type': str
                                    }
                                },
                                {
                                    'bucket': {
                                        'required': True,
                                        'keys': [],
                                        'type': str
                                    }
                                },
                                {
                                    'token': {
                                        'required': True,
                                        'keys': [],
                                        'type': str
                                    }
                                },
                                {
                                    'pruning': {
                                        'required':
                                        True,
                                        'keys': [
                                            {
                                                'task': {
                                                    'required':
                                                    True,
                                                    'keys': [
                                                        {
                                                            'name': {
                                                                'required':
                                                                True,
                                                                'keys': [],
                                                                'type': str
                                                            }
                                                        },
                                                        {
                                                            'predicate': {
                                                                'required':
                                                                True,
                                                                'keys': [],
                                                                'type': str
                                                            }
                                                        },
                                                        {
                                                            'keep_last': {
                                                                'required':
                                                                True,
                                                                'keys': [],
                                                                'type': int
                                                            }
                                                        },
                                                    ]
                                                }
                                            },
                                        ]
                                    }
                                },
                            ]
                        }
                    },
                    {
                        'mqtt': {
                            'required':
                            False,
                            'keys': [
                                {
                                    'enable': {
                                        'required': True,
                                        'keys': [],
                                        'type': bool
                                    }
                                },
                                {
                                    'client': {
                                        'required': True,
                                        'keys': [],
                                        'type': str
                                    }
                                },
                                {
                                    'ip': {
                                        'required': True,
                                        'keys': [],
                                        'type': str
                                    }
                                },
                                {
                                    'port': {
                                        'required': True,
                                        'keys': [],
                                        'type': int
                                    }
                                },
                                {
                                    'username': {
                                        'required': True,
                                        'keys': [],
                                        'type': str
                                    }
                                },
                                {
                                    'password': {
                                        'required': True,
                                        'keys': [],
                                        'type': str
                                    }
                                },
                            ]
                        }
                    },
                    {
                        'inverters': {
                            'required':
                            True,
                            'keys': [
                                {
                                    'inverter': {
                                        'required':
                                        True,
                                        'keys': [
                                            {
                                                'name': {
                                                    'required': True,
                                                    'keys': [],
                                                    'type': str
                                                }
                                            },
                                            {
                                                'url': {
                                                    'required': True,
                                                    'keys': [],
                                                    'type': str
                                                }
                                            },
                                            {
                                                'username': {
                                                    'required': True,
                                                    'keys': [],
                                                    'type': str
                                                }
                                            },
                                            {
                                                'password': {
                                                    'required': True,
                                                    'keys': [],
                                                    'type': str
                                                }
                                            },
                                        ]
                                    }
                                },
                            ]
                        }
                    },
                    {
                        'settings': {
                            'required':
                            False,
                            'keys': [
                                {
                                    'sampling': {
                                        'required':
                                        False,
                                        'keys': [
                                            {
                                                'fast': {
                                                    'required': False,
                                                    'keys': [],
                                                    'type': int
                                                }
                                            },
                                            {
                                                'medium': {
                                                    'required': False,
                                                    'keys': [],
                                                    'type': int
                                                }
                                            },
                                            {
                                                'slow': {
                                                    'required': False,
                                                    'keys': [],
                                                    'type': int
                                                }
                                            },
                                        ]
                                    }
                                },
                            ]
                        }
                    },
                ],
            },
        },
    ]

    try:
        result = check_required_keys(dict(config), required_keys)
        check_unsupported(dict(config), required_keys)
    except FailedInitialization:
        raise
    except Exception as e:
        raise FailedInitialization(f"Unexpected exception: {e}")
    return config if result else None