def do_substitution_pass(config): if CONF_SUBSTITUTIONS not in config: return config substitutions = config[CONF_SUBSTITUTIONS] if not isinstance(substitutions, dict): raise pi4homeError( u"Substitutions must be a key to value mapping, got {}" u"".format(type(substitutions))) key = '' try: replace_keys = [] for key, value in substitutions.items(): sub = validate_substitution_key(key) if sub != key: replace_keys.append((key, sub)) substitutions[key] = cv.string_strict(value) for old, new in replace_keys: substitutions[new] = substitutions[old] del substitutions[old] except vol.Invalid as err: err.path.append(key) raise pi4homeError( u"Error while parsing substitutions: {}".format(err)) config[CONF_SUBSTITUTIONS] = substitutions _substitute_item(substitutions, config, []) return config
def _load_yaml_internal(fname): """Load a YAML file.""" try: with codecs.open(fname, encoding='utf-8') as conf_file: return yaml.load(conf_file, Loader=SafeLineLoader) or OrderedDict() except yaml.YAMLError as exc: raise pi4homeError(exc) except IOError as exc: raise pi4homeError(u"Error accessing file {}: {}".format(fname, exc)) except UnicodeDecodeError as exc: _LOGGER.error(u"Unable to read file %s: %s", fname, exc) raise pi4homeError(exc)
def _ordered_dict(loader, node): """Load YAML mappings into an ordered dictionary to preserve key order.""" custom_flatten_mapping(loader, node) nodes = custom_construct_pairs(loader, node) seen = {} for (key, _), nv in zip(nodes, node.value): if isinstance(nv, yaml.ScalarNode): line = nv.start_mark.line else: line = nv[0].start_mark.line try: hash(key) except TypeError: fname = getattr(loader.stream, 'name', '') raise yaml.MarkedYAMLError( context="invalid key: \"{}\"".format(key), context_mark=yaml.Mark(fname, 0, line, -1, None, None)) if key in seen: fname = getattr(loader.stream, 'name', '') raise pi4homeError(u'YAML file {} contains duplicate key "{}". ' u'Check lines {} and {}.'.format( fname, key, seen[key], line)) seen[key] = line return _add_reference(OrderedDict(nodes), loader, node)
def initialize(config, subscriptions, on_message, username, password, client_id): def on_connect(client, userdata, flags, return_code): for topic in subscriptions: client.subscribe(topic) def on_disconnect(client, userdata, result_code): if result_code == 0: return tries = 0 while True: try: if client.reconnect() == 0: _LOGGER.info("Successfully reconnected to the MQTT server") break except socket.error: pass wait_time = min(2**tries, 300) _LOGGER.warning( "Disconnected from MQTT (%s). Trying to reconnect in %s s", result_code, wait_time) time.sleep(wait_time) tries += 1 client = mqtt.Client(client_id or u'') client.on_connect = on_connect client.on_message = on_message client.on_disconnect = on_disconnect if username is None: if config[CONF_MQTT].get(CONF_USERNAME): client.username_pw_set(config[CONF_MQTT][CONF_USERNAME], config[CONF_MQTT][CONF_PASSWORD]) elif username: client.username_pw_set(username, password) if config[CONF_MQTT].get(CONF_SSL_FINGERPRINTS): if sys.version_info >= (2, 7, 13): tls_version = ssl.PROTOCOL_TLS # pylint: disable=no-member else: tls_version = ssl.PROTOCOL_SSLv23 client.tls_set(ca_certs=None, certfile=None, keyfile=None, cert_reqs=ssl.CERT_REQUIRED, tls_version=tls_version, ciphers=None) try: client.connect(config[CONF_MQTT][CONF_BROKER], config[CONF_MQTT][CONF_PORT]) except socket.error as err: raise pi4homeError("Cannot connect to MQTT broker: {}".format(err)) try: client.loop_forever() except KeyboardInterrupt: pass return 0
def to_code(config): from PIL import Image path = CORE.relative_path(config[CONF_FILE]) try: image = Image.open(path) except Exception as e: raise core.pi4homeError(u"Could not load image file {}: {}".format(path, e)) if CONF_RESIZE in config: image.thumbnail(config[CONF_RESIZE]) image = image.convert('1', dither=Image.NONE) width, height = image.size if width > 500 or height > 500: _LOGGER.warning("The image you requested is very big. Please consider using the resize " "parameter") width8 = ((width + 7) // 8) * 8 data = [0 for _ in range(height * width8 // 8)] for y in range(height): for x in range(width): if image.getpixel((x, y)): continue pos = x + y * width8 data[pos // 8] |= 0x80 >> (pos % 8) rhs = safe_exp([HexInt(x) for x in data]) prog_arr = progmem_array(config[CONF_RAW_DATA_ID], rhs) rhs = App.make_image(prog_arr, width, height) Pvariable(config[CONF_ID], rhs)
def write_cpp(code_s): path = CORE.relative_build_path('src', 'main.cpp') if os.path.isfile(path): try: with codecs.open(path, 'r', encoding='utf-8') as f_handle: text = f_handle.read() except OSError: raise pi4homeError(u"Could not read C++ file at {}".format(path)) prev_file = text code_format = find_begin_end(text, CPP_AUTO_GENERATE_BEGIN, CPP_AUTO_GENERATE_END) code_format_ = find_begin_end(code_format[0], CPP_INCLUDE_BEGIN, CPP_INCLUDE_END) code_format = (code_format_[0], code_format_[1], code_format[1]) else: prev_file = None mkdir_p(os.path.dirname(path)) code_format = CPP_BASE_FORMAT include_s = get_include_text() full_file = code_format[ 0] + CPP_INCLUDE_BEGIN + u'\n' + include_s + CPP_INCLUDE_END full_file += code_format[ 1] + CPP_AUTO_GENERATE_BEGIN + u'\n' + code_s + CPP_AUTO_GENERATE_END full_file += code_format[2] if prev_file == full_file: return with codecs.open(path, 'w+', encoding='utf-8') as f_handle: f_handle.write(full_file)
def _secret_yaml(loader, node): """Load secrets and embed it into the configuration YAML.""" secret_path = os.path.join(os.path.dirname(loader.name), SECRET_YAML) secrets = _load_yaml_internal(secret_path) if node.value not in secrets: raise pi4homeError(u"Secret {} not defined".format(node.value)) val = secrets[node.value] _SECRET_VALUES[text_type(val)] = node.value return val
def validate_local_no_higher_than_global(value): global_level = value.get(CONF_LEVEL, 'DEBUG') for tag, level in value.get(CONF_LOGS, {}).items(): if LOG_LEVEL_SEVERITY.index(level) > LOG_LEVEL_SEVERITY.index( global_level): raise pi4homeError( u"The local log level {} for {} must be less severe than the " u"global log level {}.".format(level, tag, global_level)) return value
def _resolve_with_zeroconf(host): from pi4home.core import pi4homeError try: zc = Zeroconf() except Exception: raise pi4homeError( "Cannot start mDNS sockets, is this a docker container without " "host network mode?") try: info = zc.resolve_host(host + '.') except Exception as err: raise pi4homeError("Error resolving mDNS hostname: {}".format(err)) finally: zc.close() if info is None: raise pi4homeError( "Error resolving address with mDNS: Did not respond. " "Maybe the device is offline.") return info
def _env_var_yaml(_, node): """Load environment variables and embed it into the configuration YAML.""" args = node.value.split() # Check for a default value if len(args) > 1: return os.getenv(args[0], u' '.join(args[1:])) if args[0] in os.environ: return os.environ[args[0]] raise pi4homeError(u"Environment variable {} not defined.".format( node.value))
def resolve_ip_address(host): from pi4home.core import pi4homeError try: ip = socket.gethostbyname(host) except socket.error as err: if host.endswith('.local'): ip = _resolve_with_zeroconf(host) else: raise pi4homeError("Error resolving IP address: {}".format(err)) return ip
def find_begin_end(text, begin_s, end_s): begin_index = text.find(begin_s) if begin_index == -1: raise pi4homeError( u"Could not find auto generated code begin in file, either " u"delete the main sketch file or insert the comment again.") if text.find(begin_s, begin_index + 1) != -1: raise pi4homeError( u"Found multiple auto generate code begins, don't know " u"which to chose, please remove one of them.") end_index = text.find(end_s) if end_index == -1: raise pi4homeError( u"Could not find auto generated code end in file, either " u"delete the main sketch file or insert the comment again.") if text.find(end_s, end_index + 1) != -1: raise pi4homeError( u"Found multiple auto generate code endings, don't know " u"which to chose, please remove one of them.") return text[:begin_index], text[(end_index + len(end_s)):]
def generic_gpio_pin_expression_(conf, mock_obj, default_mode): if conf is None: return number = conf[CONF_NUMBER] inverted = conf.get(CONF_INVERTED) if CONF_PCF8574 in conf: from pi4home.components import pcf8574 for hub in CORE.get_variable(conf[CONF_PCF8574]): yield None if default_mode == u'INPUT': mode = pcf8574.PCF8675_GPIO_MODES[conf.get(CONF_MODE, u'INPUT')] yield hub.make_input_pin(number, mode, inverted) return if default_mode == u'OUTPUT': yield hub.make_output_pin(number, inverted) return raise pi4homeError(u"Unknown default mode {}".format(default_mode)) if CONF_MCP23017 in conf: from pi4home.components import mcp23017 for hub in CORE.get_variable(conf[CONF_MCP23017]): yield None if default_mode == u'INPUT': mode = mcp23017.MCP23017_GPIO_MODES[conf.get(CONF_MODE, u'INPUT')] yield hub.make_input_pin(number, mode, inverted) return if default_mode == u'OUTPUT': yield hub.make_output_pin(number, inverted) return raise pi4homeError(u"Unknown default mode {}".format(default_mode)) if len(conf) == 1: yield IntLiteral(number) return mode = RawExpression(conf.get(CONF_MODE, default_mode)) yield mock_obj(number, mode, inverted)
def show_logs(config, args, port): if 'logger' not in config: raise pi4homeError("Logger is not configured!") if get_port_type(port) == 'SERIAL': run_miniterm(config, port) return 0 if get_port_type(port) == 'NETWORK' and 'api' in config: return run_logs(config, port) if get_port_type(port) == 'MQTT' and 'mqtt' in config: return mqtt.show_logs(config, args.topic, args.username, args.password, args.client_id) raise ValueError
def preload_core_config(config): if 'pi4homeyaml' in config: _LOGGER.warning("The pi4homeyaml section has been renamed to pi4home in 1.11.0. " "Please replace 'pi4homeyaml:' in your configuration with 'pi4home:'.") config[CONF_PI4HOME] = config.pop('pi4homeyaml') if CONF_PI4HOME not in config: raise pi4homeError(u"No pi4home section in config") core_conf = config[CONF_PI4HOME] if CONF_PLATFORM not in core_conf: raise pi4homeError("pi4home.platform not specified.") if CONF_BOARD not in core_conf: raise pi4homeError("pi4home.board not specified.") if CONF_NAME not in core_conf: raise pi4homeError("pi4home.name not specified.") try: CORE.esp_platform = validate_platform(core_conf[CONF_PLATFORM]) CORE.board = validate_board(core_conf[CONF_BOARD]) CORE.name = cv.valid_name(core_conf[CONF_NAME]) CORE.build_path = CORE.relative_path( cv.string(core_conf.get(CONF_BUILD_PATH, default_build_path()))) except vol.Invalid as e: raise pi4homeError(text_type(e))
def custom_construct_pairs(loader, node): pairs = [] for kv in node.value: if isinstance(kv, yaml.ScalarNode): obj = loader.construct_object(kv) if not isinstance(obj, dict): raise pi4homeError( "Expected mapping for anchored include tag, got {}".format( type(obj))) for key, value in obj.items(): pairs.append((key, value)) else: key_node, value_node = kv key = loader.construct_object(key_node) value = loader.construct_object(value_node) pairs.append((key, value)) return pairs
def load_config(): try: config = yaml_util.load_yaml(CORE.config_path) except OSError: raise pi4homeError( u"Invalid YAML at {}. Please see YAML syntax reference or use an online " u"YAML syntax validator".format(CORE.config_path)) CORE.raw_config = config config = substitutions.do_substitution_pass(config) core_config.preload_core_config(config) try: result = validate_config(config) except pi4homeError: raise except Exception: _LOGGER.error(u"Unexpected exception while reading configuration:") raise return result
def write_platformio_ini(content, path): symlink_pi4home_core_version(CORE.pi4home_core_version) update_pi4home_core_repo() update_storage_json() if os.path.isfile(path): try: with codecs.open(path, 'r', encoding='utf-8') as f_handle: text = f_handle.read() except OSError: raise pi4homeError(u"Could not read ini file at {}".format(path)) prev_file = text content_format = find_begin_end(text, INI_AUTO_GENERATE_BEGIN, INI_AUTO_GENERATE_END) else: prev_file = None content_format = INI_BASE_FORMAT full_file = content_format[0] + INI_AUTO_GENERATE_BEGIN + '\n' + content full_file += INI_AUTO_GENERATE_END + content_format[1] if prev_file == full_file: return with codecs.open(path, mode='w+', encoding='utf-8') as f_handle: f_handle.write(full_file)