diff --git a/.clang-format b/.clang-format index a7c337f80e..f2d86c57cd 100644 --- a/.clang-format +++ b/.clang-format @@ -49,7 +49,7 @@ ConstructorInitializerAllOnOneLineOrOnePerLine: true ConstructorInitializerIndentWidth: 4 ContinuationIndentWidth: 4 Cpp11BracedListStyle: true -DerivePointerAlignment: true +DerivePointerAlignment: false DisableFormat: false ExperimentalAutoDetectBinPacking: false FixNamespaceComments: true diff --git a/.clang-tidy b/.clang-tidy index 5e486e6a0c..fc5ce854f1 100644 --- a/.clang-tidy +++ b/.clang-tidy @@ -4,14 +4,24 @@ Checks: >- -abseil-*, -android-*, -boost-*, - -bugprone-macro-parentheses, + -bugprone-branch-clone, + -bugprone-narrowing-conversions, + -bugprone-signed-char-misuse, + -bugprone-too-small-loop-variable, -cert-dcl50-cpp, -cert-err58-cpp, - -clang-analyzer-core.CallAndMessage, + -cert-oop57-cpp, + -cert-str34-c, + -clang-analyzer-optin.cplusplus.UninitializedObject, -clang-analyzer-osx.*, - -clang-analyzer-security.*, + -clang-diagnostic-shadow-field, + -cppcoreguidelines-avoid-c-arrays, -cppcoreguidelines-avoid-goto, - -cppcoreguidelines-c-copy-assignment-signature, + -cppcoreguidelines-avoid-magic-numbers, + -cppcoreguidelines-init-variables, + -cppcoreguidelines-macro-usage, + -cppcoreguidelines-narrowing-conversions, + -cppcoreguidelines-non-private-member-variables-in-classes, -cppcoreguidelines-owning-memory, -cppcoreguidelines-pro-bounds-array-to-pointer-decay, -cppcoreguidelines-pro-bounds-constant-array-index, @@ -24,40 +34,51 @@ Checks: >- -cppcoreguidelines-pro-type-union-access, -cppcoreguidelines-pro-type-vararg, -cppcoreguidelines-special-member-functions, - -fuchsia-*, -fuchsia-default-arguments, -fuchsia-multiple-inheritance, -fuchsia-overloaded-operator, -fuchsia-statically-constructed-objects, + -fuchsia-default-arguments-declarations, + -fuchsia-default-arguments-calls, -google-build-using-namespace, -google-explicit-constructor, -google-readability-braces-around-statements, -google-readability-casting, -google-readability-todo, - -google-runtime-int, -google-runtime-references, -hicpp-*, + -llvm-else-after-return, -llvm-header-guard, -llvm-include-order, - -misc-unconventional-assign-operator, + -llvm-qualified-auto, + -llvmlibc-*, + -misc-non-private-member-variables-in-classes, + -misc-no-recursion, -misc-unused-parameters, - -modernize-deprecated-headers, - -modernize-pass-by-value, - -modernize-pass-by-value, + -modernize-avoid-c-arrays, -modernize-return-braced-init-list, -modernize-use-auto, -modernize-use-default-member-init, -modernize-use-equals-default, + -modernize-use-trailing-return-type, -mpi-*, -objc-*, - -performance-unnecessary-value-param, -readability-braces-around-statements, + -readability-const-return-type, + -readability-convert-member-functions-to-static, -readability-else-after-return, -readability-implicit-bool-conversion, + -readability-isolate-declaration, + -readability-magic-numbers, + -readability-make-member-function-const, -readability-named-parameter, + -readability-qualified-auto, + -readability-redundant-access-specifiers, -readability-redundant-member-init, - -warnings-as-errors, - -zircon-* + -readability-redundant-string-init, + -readability-uppercase-literal-suffix, + -readability-use-anyofallof, + -warnings-as-errors WarningsAsErrors: '*' HeaderFilterRegex: '^.*/src/esphome/.*' AnalyzeTemporaryDtors: false diff --git a/.devcontainer/devcontainer.json b/.devcontainer/devcontainer.json index 8f7d751437..3904962d7c 100644 --- a/.devcontainer/devcontainer.json +++ b/.devcontainer/devcontainer.json @@ -2,16 +2,29 @@ "name": "ESPHome Dev", "context": "..", "dockerFile": "../docker/Dockerfile.dev", - "postCreateCommand": "mkdir -p config && pip3 install -e .", - "runArgs": ["--privileged", "-e", "ESPHOME_DASHBOARD_USE_PING=1"], + "postCreateCommand": [ + "script/devcontainer-post-create" + ], + "runArgs": [ + "--privileged", + "-e", + "ESPHOME_DASHBOARD_USE_PING=1" + ], "appPort": 6052, "extensions": [ + // python "ms-python.python", "visualstudioexptteam.vscodeintellicode", - "redhat.vscode-yaml" + // yaml + "redhat.vscode-yaml", + // cpp + "ms-vscode.cpptools", + // editorconfig + "editorconfig.editorconfig", ], "settings": { - "python.pythonPath": "/usr/local/bin/python", + "python.languageServer": "Pylance", + "python.pythonPath": "/usr/bin/python3", "python.linting.pylintEnabled": true, "python.linting.enabled": true, "python.formatting.provider": "black", @@ -19,7 +32,7 @@ "editor.formatOnSave": true, "editor.formatOnType": true, "files.trimTrailingWhitespace": true, - "terminal.integrated.shell.linux": "/bin/bash", + "terminal.integrated.defaultProfile.linux": "bash", "yaml.customTags": [ "!secret scalar", "!lambda scalar", @@ -27,6 +40,18 @@ "!include_dir_list scalar", "!include_dir_merge_list scalar", "!include_dir_merge_named scalar" - ] + ], + "files.exclude": { + "**/.git": true, + "**/.DS_Store": true, + "**/*.pyc": { + "when": "$(basename).py" + }, + "**/__pycache__": true + }, + "files.associations": { + "**/.vscode/*.json": "jsonc" + }, + "C_Cpp.clang_format_path": "/usr/bin/clang-format-11", } } diff --git a/.dockerignore b/.dockerignore index e1baed38ca..9f14b98059 100644 --- a/.dockerignore +++ b/.dockerignore @@ -103,6 +103,10 @@ venv.bak/ # mypy .mypy_cache/ +# PlatformIO +.pio/ + +# ESPHome config/ examples/ Dockerfile diff --git a/.editorconfig b/.editorconfig index 29cbb1e32f..8ccf1eeebc 100644 --- a/.editorconfig +++ b/.editorconfig @@ -7,7 +7,7 @@ insert_final_newline = true charset = utf-8 # python -[*.{py}] +[*.py] indent_style = space indent_size = 4 @@ -25,4 +25,10 @@ indent_size = 2 [*.{yaml,yml}] indent_style = space indent_size = 2 -quote_type = single \ No newline at end of file +quote_type = single + +# JSON +[*.json] +indent_style = space +indent_size = 2 + diff --git a/.github/FUNDING.yml b/.github/FUNDING.yml index 52ac3648b0..864586fe6b 100644 --- a/.github/FUNDING.yml +++ b/.github/FUNDING.yml @@ -1,8 +1,3 @@ # These are supported funding model platforms -github: -patreon: ottowinter -open_collective: -ko_fi: -tidelift: -custom: https://esphome.io/guides/supporters.html +custom: https://www.nabucasa.com diff --git a/.github/PULL_REQUEST_TEMPLATE.md b/.github/PULL_REQUEST_TEMPLATE.md index 0d77eee7aa..aa90ef365f 100644 --- a/.github/PULL_REQUEST_TEMPLATE.md +++ b/.github/PULL_REQUEST_TEMPLATE.md @@ -1,25 +1,22 @@ # What does this implement/fix? -Quick description +Quick description and explanation of changes ## Types of changes - [ ] Bugfix (non-breaking change which fixes an issue) - [ ] New feature (non-breaking change which adds functionality) - [ ] Breaking change (fix or feature that would cause existing functionality to not work as expected) -- [ ] Configuration change (this will require users to update their yaml configuration files to keep working) +- [ ] Other **Related issue or feature (if applicable):** fixes **Pull request in [esphome-docs](https://github.com/esphome/esphome-docs) with documentation (if applicable):** esphome/esphome-docs# - -# Test Environment + +## Test Environment - [ ] ESP32 - [ ] ESP8266 -- [ ] Windows -- [ ] Mac OS -- [ ] Linux ## Example entry for `config.yaml`: - - - - - - - - - - - - - - - - - - - - - - - - -
- - add - -
- - - - - - - - - - - - {% if begin and len(entries) == 1 %} - - {% end %} - - - - diff --git a/esphome/dashboard/templates/login.html b/esphome/dashboard/templates/login.html deleted file mode 100644 index f8d1116dff..0000000000 --- a/esphome/dashboard/templates/login.html +++ /dev/null @@ -1,93 +0,0 @@ - - - - - - - Login - ESPHome - - - - - - - - - - - - -
-
-
-
-
-
-
- - v{{ version }} - Dashboard Login -

- {% if hassio %} - Login by entering your Home Assistant login credentials. - {% else %} - Login by entering your ESPHome login credentials. - {% end %} -

- - {% if error is not None %} -
- Error! - {{ escape(error) }} -
- - - {% end %} - -
- {% if has_username or hassio %} -
-
- person - - -
-
- {% end %} - -
-
- lock - - -
-
-
-
- -
- -
-
-
-
-
- - - -
-
- - - diff --git a/esphome/espota2.py b/esphome/espota2.py index 785cb155df..351f6feda9 100644 --- a/esphome/espota2.py +++ b/esphome/espota2.py @@ -296,15 +296,14 @@ def run_ota_impl_(remote_host, remote_port, password, filename): _LOGGER.error("Connecting to %s:%s failed: %s", remote_host, remote_port, err) return 1 - file_handle = open(filename, "rb") - try: - perform_ota(sock, password, file_handle, filename) - except OTAError as err: - _LOGGER.error(str(err)) - return 1 - finally: - sock.close() - file_handle.close() + with open(filename, "rb") as file_handle: + try: + perform_ota(sock, password, file_handle, filename) + except OTAError as err: + _LOGGER.error(str(err)) + return 1 + finally: + sock.close() return 0 diff --git a/esphome/final_validate.py b/esphome/final_validate.py new file mode 100644 index 0000000000..199c68210e --- /dev/null +++ b/esphome/final_validate.py @@ -0,0 +1,82 @@ +from abc import ABC, abstractmethod +from typing import Dict, Any +import contextvars + +from esphome.types import ConfigFragmentType, ID, ConfigPathType +import esphome.config_validation as cv +from esphome.const import ( + ARDUINO_VERSION_ESP32, + ARDUINO_VERSION_ESP8266, + CONF_ESPHOME, + CONF_ARDUINO_VERSION, +) +from esphome.core import CORE + + +class FinalValidateConfig(ABC): + @property + @abstractmethod + def data(self) -> Dict[str, Any]: + """A dictionary that can be used by post validation functions to store + global data during the validation phase. Each component should store its + data under a unique key + """ + + @abstractmethod + def get_path_for_id(self, id: ID) -> ConfigPathType: + """Get the config path a given ID has been declared in. + + This is the location under the _validated_ config (for example, with cv.ensure_list applied) + Raises KeyError if the id was not declared in the configuration. + """ + + @abstractmethod + def get_config_for_path(self, path: ConfigPathType) -> ConfigFragmentType: + """Get the config fragment for the given global path. + + Raises KeyError if a key in the path does not exist. + """ + + +FinalValidateConfig.register(dict) + +# Context variable tracking the full config for some final validation functions. +full_config: contextvars.ContextVar[FinalValidateConfig] = contextvars.ContextVar( + "full_config" +) + + +def id_declaration_match_schema(schema): + """A final-validation schema function that applies a schema to the outer config fragment of an + ID declaration. + + This validator must be applied to ID values. + """ + if not isinstance(schema, cv.Schema): + schema = cv.Schema(schema, extra=cv.ALLOW_EXTRA) + + def validator(value): + fconf = full_config.get() + path = fconf.get_path_for_id(value)[:-1] + declaration_config = fconf.get_config_for_path(path) + with cv.prepend_path([cv.ROOT_CONFIG_PATH] + path): + return schema(declaration_config) + + return validator + + +def get_arduino_framework_version(): + path = [CONF_ESPHOME, CONF_ARDUINO_VERSION] + # This is run after core validation, so the property is set even if user didn't + version: str = full_config.get().get_config_for_path(path) + + if CORE.is_esp32: + version_map = ARDUINO_VERSION_ESP32 + elif CORE.is_esp8266: + version_map = ARDUINO_VERSION_ESP8266 + else: + raise ValueError("Platform not supported yet for this validator") + + reverse_map = {v: k for k, v in version_map.items()} + framework_version = reverse_map.get(version) + return framework_version diff --git a/esphome/helpers.py b/esphome/helpers.py index 780a2aa88e..ad7b8272b2 100644 --- a/esphome/helpers.py +++ b/esphome/helpers.py @@ -57,24 +57,13 @@ def cpp_string_escape(string, encoding="utf-8"): return '"' + result + '"' -def color(the_color, message=""): - from colorlog.escape_codes import escape_codes, parse_colors - - if not message: - res = parse_colors(the_color) - else: - res = parse_colors(the_color) + message + escape_codes["reset"] - - return res - - def run_system_command(*args): import subprocess - p = subprocess.Popen(args, stdout=subprocess.PIPE, stderr=subprocess.PIPE) - stdout, stderr = p.communicate() - rc = p.returncode - return rc, stdout, stderr + with subprocess.Popen(args, stdout=subprocess.PIPE, stderr=subprocess.PIPE) as p: + stdout, stderr = p.communicate() + rc = p.returncode + return rc, stdout, stderr def mkdir_p(path): @@ -233,7 +222,7 @@ def write_file_if_changed(path: Union[Path, str], text: str): write_file(path, text) -def copy_file_if_changed(src, dst): +def copy_file_if_changed(src: os.PathLike, dst: os.PathLike) -> None: import shutil if file_compare(src, dst): @@ -251,7 +240,7 @@ def list_starts_with(list_, sub): return len(sub) <= len(list_) and all(list_[i] == x for i, x in enumerate(sub)) -def file_compare(path1, path2): +def file_compare(path1: os.PathLike, path2: os.PathLike) -> bool: """Return True if the files path1 and path2 have the same contents.""" import stat diff --git a/esphome/loader.py b/esphome/loader.py new file mode 100644 index 0000000000..f74fc6367d --- /dev/null +++ b/esphome/loader.py @@ -0,0 +1,189 @@ +import logging +import typing +from typing import Callable, List, Optional, Dict, Any, ContextManager +from types import ModuleType +import importlib +import importlib.util +import importlib.resources +import importlib.abc +import sys +from pathlib import Path + +from esphome.const import ESP_PLATFORMS, SOURCE_FILE_EXTENSIONS +import esphome.core.config +from esphome.core import CORE +from esphome.types import ConfigType + +_LOGGER = logging.getLogger(__name__) + + +class SourceFile: + def __init__( + self, + package: importlib.resources.Package, + resource: importlib.resources.Resource, + ) -> None: + self._package = package + self._resource = resource + + def open_binary(self) -> typing.BinaryIO: + return importlib.resources.open_binary(self._package, self._resource) + + def path(self) -> ContextManager[Path]: + return importlib.resources.path(self._package, self._resource) + + +class ComponentManifest: + def __init__(self, module: ModuleType): + self.module = module + + @property + def package(self) -> str: + return self.module.__package__ + + @property + def is_platform(self) -> bool: + return len(self.module.__name__.split(".")) == 4 + + @property + def is_platform_component(self) -> bool: + return getattr(self.module, "IS_PLATFORM_COMPONENT", False) + + @property + def config_schema(self) -> Optional[Any]: + return getattr(self.module, "CONFIG_SCHEMA", None) + + @property + def multi_conf(self) -> bool: + return getattr(self.module, "MULTI_CONF", False) + + @property + def to_code(self) -> Optional[Callable[[Any], None]]: + return getattr(self.module, "to_code", None) + + @property + def esp_platforms(self) -> List[str]: + return getattr(self.module, "ESP_PLATFORMS", ESP_PLATFORMS) + + @property + def dependencies(self) -> List[str]: + return getattr(self.module, "DEPENDENCIES", []) + + @property + def conflicts_with(self) -> List[str]: + return getattr(self.module, "CONFLICTS_WITH", []) + + @property + def auto_load(self) -> List[str]: + return getattr(self.module, "AUTO_LOAD", []) + + @property + def codeowners(self) -> List[str]: + return getattr(self.module, "CODEOWNERS", []) + + @property + def final_validate_schema(self) -> Optional[Callable[[ConfigType], None]]: + """Components can declare a `FINAL_VALIDATE_SCHEMA` cv.Schema that gets called + after the main validation. In that function checks across components can be made. + + Note that the function can't mutate the configuration - no changes are saved + """ + return getattr(self.module, "FINAL_VALIDATE_SCHEMA", None) + + @property + def source_files(self) -> Dict[Path, SourceFile]: + ret = {} + for resource in importlib.resources.contents(self.package): + if Path(resource).suffix not in SOURCE_FILE_EXTENSIONS: + continue + if not importlib.resources.is_resource(self.package, resource): + # Not a resource = this is a directory (yeah this is confusing) + continue + # Always use / for C++ include names + target_path = Path(*self.package.split(".")) / resource + ret[target_path] = SourceFile(self.package, resource) + return ret + + +class ComponentMetaFinder(importlib.abc.MetaPathFinder): + def __init__( + self, components_path: Path, allowed_components: Optional[List[str]] = None + ) -> None: + self._allowed_components = allowed_components + self._finders = [] + for hook in sys.path_hooks: + try: + finder = hook(str(components_path)) + except ImportError: + continue + self._finders.append(finder) + + def find_spec(self, fullname: str, path: Optional[List[str]], target=None): + if not fullname.startswith("esphome.components."): + return None + parts = fullname.split(".") + if len(parts) != 3: + # only handle direct components, not platforms + # platforms are handled automatically when parent is imported + return None + component = parts[2] + if ( + self._allowed_components is not None + and component not in self._allowed_components + ): + return None + + for finder in self._finders: + spec = finder.find_spec(fullname, target=target) + if spec is not None: + return spec + return None + + +def clear_component_meta_finders(): + sys.meta_path = [x for x in sys.meta_path if not isinstance(x, ComponentMetaFinder)] + + +def install_meta_finder( + components_path: Path, allowed_components: Optional[List[str]] = None +): + sys.meta_path.insert(0, ComponentMetaFinder(components_path, allowed_components)) + + +def install_custom_components_meta_finder(): + custom_components_dir = (Path(CORE.config_dir) / "custom_components").resolve() + install_meta_finder(custom_components_dir) + + +def _lookup_module(domain): + if domain in _COMPONENT_CACHE: + return _COMPONENT_CACHE[domain] + + try: + module = importlib.import_module(f"esphome.components.{domain}") + except ImportError as e: + if "No module named" not in str(e): + _LOGGER.error("Unable to import component %s:", domain, exc_info=True) + return None + except Exception: # pylint: disable=broad-except + _LOGGER.error("Unable to load component %s:", domain, exc_info=True) + return None + else: + manif = ComponentManifest(module) + _COMPONENT_CACHE[domain] = manif + return manif + + +def get_component(domain): + assert "." not in domain + return _lookup_module(domain) + + +def get_platform(domain, platform): + full = f"{platform}.{domain}" + return _lookup_module(full) + + +_COMPONENT_CACHE = {} +CORE_COMPONENTS_PATH = (Path(__file__).parent / "components").resolve() +_COMPONENT_CACHE["esphome"] = ComponentManifest(esphome.core.config) diff --git a/esphome/log.py b/esphome/log.py new file mode 100644 index 0000000000..fa79efa833 --- /dev/null +++ b/esphome/log.py @@ -0,0 +1,82 @@ +import logging + +from esphome.core import CORE + + +class AnsiFore: + KEEP = "" + BLACK = "\033[30m" + RED = "\033[31m" + GREEN = "\033[32m" + YELLOW = "\033[33m" + BLUE = "\033[34m" + MAGENTA = "\033[35m" + CYAN = "\033[36m" + WHITE = "\033[37m" + RESET = "\033[39m" + + BOLD_BLACK = "\033[1;30m" + BOLD_RED = "\033[1;31m" + BOLD_GREEN = "\033[1;32m" + BOLD_YELLOW = "\033[1;33m" + BOLD_BLUE = "\033[1;34m" + BOLD_MAGENTA = "\033[1;35m" + BOLD_CYAN = "\033[1;36m" + BOLD_WHITE = "\033[1;37m" + BOLD_RESET = "\033[1;39m" + + +class AnsiStyle: + BRIGHT = "\033[1m" + BOLD = "\033[1m" + DIM = "\033[2m" + THIN = "\033[2m" + NORMAL = "\033[22m" + RESET_ALL = "\033[0m" + + +Fore = AnsiFore() +Style = AnsiStyle() + + +def color(col: str, msg: str, reset: bool = True) -> bool: + if col and not col.startswith("\033["): + raise ValueError("Color must be value from esphome.log.Fore") + s = str(col) + msg + if reset and col: + s += str(Style.RESET_ALL) + return s + + +class ESPHomeLogFormatter(logging.Formatter): + def __init__(self): + super().__init__(fmt="%(levelname)s %(message)s", datefmt="%H:%M:%S", style="%") + + def format(self, record): + formatted = super().format(record) + prefix = { + "DEBUG": Fore.CYAN, + "INFO": Fore.GREEN, + "WARNING": Fore.YELLOW, + "ERROR": Fore.RED, + "CRITICAL": Fore.RED, + }.get(record.levelname, "") + return f"{prefix}{formatted}{Style.RESET_ALL}" + + +def setup_log(debug=False, quiet=False): + import colorama + + if debug: + log_level = logging.DEBUG + CORE.verbose = True + elif quiet: + log_level = logging.CRITICAL + else: + log_level = logging.INFO + logging.basicConfig(level=log_level) + + logging.getLogger("urllib3").setLevel(logging.WARNING) + + colorama.init() + logging.getLogger().handlers[0].setFormatter(ESPHomeLogFormatter()) diff --git a/esphome/mqtt.py b/esphome/mqtt.py index 86937ba37e..9be87b5c5d 100644 --- a/esphome/mqtt.py +++ b/esphome/mqtt.py @@ -22,7 +22,7 @@ from esphome.const import ( CONF_USERNAME, ) from esphome.core import CORE, EsphomeError -from esphome.helpers import color +from esphome.log import color, Fore from esphome.util import safe_print _LOGGER = logging.getLogger(__name__) @@ -158,7 +158,7 @@ def get_fingerprint(config): sha1 = hashlib.sha1(cert_der).hexdigest() - safe_print("SHA1 Fingerprint: " + color("cyan", sha1)) + safe_print("SHA1 Fingerprint: " + color(Fore.CYAN, sha1)) safe_print( "Copy the string above into mqtt.ssl_fingerprints section of {}" "".format(CORE.config_path) diff --git a/esphome/pins.py b/esphome/pins.py index fef77f3946..ff4ed9d9c1 100644 --- a/esphome/pins.py +++ b/esphome/pins.py @@ -4,877 +4,22 @@ import esphome.config_validation as cv from esphome.const import CONF_INVERTED, CONF_MODE, CONF_NUMBER from esphome.core import CORE from esphome.util import SimpleRegistry +from esphome import boards _LOGGER = logging.getLogger(__name__) -ESP8266_BASE_PINS = { - "A0": 17, - "SS": 15, - "MOSI": 13, - "MISO": 12, - "SCK": 14, - "SDA": 4, - "SCL": 5, - "RX": 3, - "TX": 1, -} - -ESP8266_BOARD_PINS = { - "d1": { - "D0": 3, - "D1": 1, - "D2": 16, - "D3": 5, - "D4": 4, - "D5": 14, - "D6": 12, - "D7": 13, - "D8": 0, - "D9": 2, - "D10": 15, - "D11": 13, - "D12": 14, - "D13": 14, - "D14": 4, - "D15": 5, - "LED": 2, - }, - "d1_mini": { - "D0": 16, - "D1": 5, - "D2": 4, - "D3": 0, - "D4": 2, - "D5": 14, - "D6": 12, - "D7": 13, - "D8": 15, - "LED": 2, - }, - "d1_mini_lite": "d1_mini", - "d1_mini_pro": "d1_mini", - "esp01": {}, - "esp01_1m": {}, - "esp07": {}, - "esp12e": {}, - "esp210": {}, - "esp8285": {}, - "esp_wroom_02": {}, - "espduino": {"LED": 16}, - "espectro": {"LED": 15, "BUTTON": 2}, - "espino": {"LED": 2, "LED_RED": 2, "LED_GREEN": 4, "LED_BLUE": 5, "BUTTON": 0}, - "espinotee": {"LED": 16}, - "espresso_lite_v1": {"LED": 16}, - "espresso_lite_v2": {"LED": 2}, - "gen4iod": {}, - "heltec_wifi_kit_8": "d1_mini", - "huzzah": { - "LED": 0, - "LED_RED": 0, - "LED_BLUE": 2, - "D4": 4, - "D5": 5, - "D12": 12, - "D13": 13, - "D14": 14, - "D15": 15, - "D16": 16, - }, - "inventone": {}, - "modwifi": {}, - "nodemcu": { - "D0": 16, - "D1": 5, - "D2": 4, - "D3": 0, - "D4": 2, - "D5": 14, - "D6": 12, - "D7": 13, - "D8": 15, - "D9": 3, - "D10": 1, - "LED": 16, - }, - "nodemcuv2": "nodemcu", - "oak": { - "P0": 2, - "P1": 5, - "P2": 0, - "P3": 3, - "P4": 1, - "P5": 4, - "P6": 15, - "P7": 13, - "P8": 12, - "P9": 14, - "P10": 16, - "P11": 17, - "LED": 5, - }, - "phoenix_v1": {"LED": 16}, - "phoenix_v2": {"LED": 2}, - "sparkfunBlynk": "thing", - "thing": {"LED": 5, "SDA": 2, "SCL": 14}, - "thingdev": "thing", - "wifi_slot": {"LED": 2}, - "wifiduino": { - "D0": 3, - "D1": 1, - "D2": 2, - "D3": 0, - "D4": 4, - "D5": 5, - "D6": 16, - "D7": 14, - "D8": 12, - "D9": 13, - "D10": 15, - "D11": 13, - "D12": 12, - "D13": 14, - }, - "wifinfo": { - "LED": 12, - "D0": 16, - "D1": 5, - "D2": 4, - "D3": 0, - "D4": 2, - "D5": 14, - "D6": 12, - "D7": 13, - "D8": 15, - "D9": 3, - "D10": 1, - }, - "wio_link": {"LED": 2, "GROVE": 15, "D0": 14, "D1": 12, "D2": 13, "BUTTON": 0}, - "wio_node": {"LED": 2, "GROVE": 15, "D0": 3, "D1": 5, "BUTTON": 0}, - "xinabox_cw01": {"SDA": 2, "SCL": 14, "LED": 5, "LED_RED": 12, "LED_GREEN": 13}, -} - -FLASH_SIZE_1_MB = 2 ** 20 -FLASH_SIZE_512_KB = FLASH_SIZE_1_MB // 2 -FLASH_SIZE_2_MB = 2 * FLASH_SIZE_1_MB -FLASH_SIZE_4_MB = 4 * FLASH_SIZE_1_MB -FLASH_SIZE_16_MB = 16 * FLASH_SIZE_1_MB - -ESP8266_FLASH_SIZES = { - "d1": FLASH_SIZE_4_MB, - "d1_mini": FLASH_SIZE_4_MB, - "d1_mini_lite": FLASH_SIZE_1_MB, - "d1_mini_pro": FLASH_SIZE_16_MB, - "esp01": FLASH_SIZE_512_KB, - "esp01_1m": FLASH_SIZE_1_MB, - "esp07": FLASH_SIZE_4_MB, - "esp12e": FLASH_SIZE_4_MB, - "esp210": FLASH_SIZE_4_MB, - "esp8285": FLASH_SIZE_1_MB, - "esp_wroom_02": FLASH_SIZE_2_MB, - "espduino": FLASH_SIZE_4_MB, - "espectro": FLASH_SIZE_4_MB, - "espino": FLASH_SIZE_4_MB, - "espinotee": FLASH_SIZE_4_MB, - "espresso_lite_v1": FLASH_SIZE_4_MB, - "espresso_lite_v2": FLASH_SIZE_4_MB, - "gen4iod": FLASH_SIZE_512_KB, - "heltec_wifi_kit_8": FLASH_SIZE_4_MB, - "huzzah": FLASH_SIZE_4_MB, - "inventone": FLASH_SIZE_4_MB, - "modwifi": FLASH_SIZE_2_MB, - "nodemcu": FLASH_SIZE_4_MB, - "nodemcuv2": FLASH_SIZE_4_MB, - "oak": FLASH_SIZE_4_MB, - "phoenix_v1": FLASH_SIZE_4_MB, - "phoenix_v2": FLASH_SIZE_4_MB, - "sparkfunBlynk": FLASH_SIZE_4_MB, - "thing": FLASH_SIZE_512_KB, - "thingdev": FLASH_SIZE_512_KB, - "wifi_slot": FLASH_SIZE_1_MB, - "wifiduino": FLASH_SIZE_4_MB, - "wifinfo": FLASH_SIZE_1_MB, - "wio_link": FLASH_SIZE_4_MB, - "wio_node": FLASH_SIZE_4_MB, - "xinabox_cw01": FLASH_SIZE_4_MB, -} - -ESP8266_LD_SCRIPTS = { - FLASH_SIZE_512_KB: ("eagle.flash.512k0.ld", "eagle.flash.512k.ld"), - FLASH_SIZE_1_MB: ("eagle.flash.1m0.ld", "eagle.flash.1m.ld"), - FLASH_SIZE_2_MB: ("eagle.flash.2m.ld", "eagle.flash.2m.ld"), - FLASH_SIZE_4_MB: ("eagle.flash.4m.ld", "eagle.flash.4m.ld"), - FLASH_SIZE_16_MB: ("eagle.flash.16m.ld", "eagle.flash.16m14m.ld"), -} - -ESP32_BASE_PINS = { - "TX": 1, - "RX": 3, - "SDA": 21, - "SCL": 22, - "SS": 5, - "MOSI": 23, - "MISO": 19, - "SCK": 18, - "A0": 36, - "A3": 39, - "A4": 32, - "A5": 33, - "A6": 34, - "A7": 35, - "A10": 4, - "A11": 0, - "A12": 2, - "A13": 15, - "A14": 13, - "A15": 12, - "A16": 14, - "A17": 27, - "A18": 25, - "A19": 26, - "T0": 4, - "T1": 0, - "T2": 2, - "T3": 15, - "T4": 13, - "T5": 12, - "T6": 14, - "T7": 27, - "T8": 33, - "T9": 32, - "DAC1": 25, - "DAC2": 26, - "SVP": 36, - "SVN": 39, -} - -ESP32_BOARD_PINS = { - "alksesp32": { - "A0": 32, - "A1": 33, - "A2": 25, - "A3": 26, - "A4": 27, - "A5": 14, - "A6": 12, - "A7": 15, - "D0": 40, - "D1": 41, - "D10": 19, - "D11": 21, - "D12": 22, - "D13": 23, - "D2": 15, - "D3": 2, - "D4": 0, - "D5": 4, - "D6": 16, - "D7": 17, - "D8": 5, - "D9": 18, - "DHT_PIN": 26, - "LED": 23, - "L_B": 5, - "L_G": 17, - "L_R": 22, - "L_RGB_B": 16, - "L_RGB_G": 21, - "L_RGB_R": 4, - "L_Y": 23, - "MISO": 22, - "MOSI": 21, - "PHOTO": 25, - "PIEZO1": 19, - "PIEZO2": 18, - "POT1": 32, - "POT2": 33, - "S1": 4, - "S2": 16, - "S3": 18, - "S4": 19, - "S5": 21, - "SCK": 23, - "SCL": 14, - "SDA": 27, - "SS": 19, - "SW1": 15, - "SW2": 2, - "SW3": 0, - }, - "bpi-bit": { - "BUTTON_A": 35, - "BUTTON_B": 27, - "BUZZER": 25, - "LIGHT_SENSOR1": 36, - "LIGHT_SENSOR2": 39, - "MPU9250_INT": 0, - "P0": 25, - "P1": 32, - "P10": 26, - "P11": 27, - "P12": 2, - "P13": 18, - "P14": 19, - "P15": 23, - "P16": 5, - "P19": 22, - "P2": 33, - "P20": 21, - "P3": 13, - "P4": 15, - "P5": 35, - "P6": 12, - "P7": 14, - "P8": 16, - "P9": 17, - "RGB_LED": 4, - "TEMPERATURE_SENSOR": 34, - }, - "d-duino-32": { - "D1": 5, - "D10": 1, - "D2": 4, - "D3": 0, - "D4": 2, - "D5": 14, - "D6": 12, - "D7": 13, - "D8": 15, - "D9": 3, - "MISO": 12, - "MOSI": 13, - "SCK": 14, - "SCL": 4, - "SDA": 5, - "SS": 15, - }, - "esp-wrover-kit": {}, - "esp32-devkitlipo": {}, - "esp32-evb": { - "BUTTON": 34, - "MISO": 15, - "MOSI": 2, - "SCK": 14, - "SCL": 16, - "SDA": 13, - "SS": 17, - }, - "esp32-gateway": {"BUTTON": 34, "LED": 33, "SCL": 16, "SDA": 32}, - "esp32-poe-iso": { - "BUTTON": 34, - "MISO": 15, - "MOSI": 2, - "SCK": 14, - "SCL": 16, - "SDA": 13, - }, - "esp32-poe": {"BUTTON": 34, "MISO": 15, "MOSI": 2, "SCK": 14, "SCL": 16, "SDA": 13}, - "esp32-pro": { - "BUTTON": 34, - "MISO": 15, - "MOSI": 2, - "SCK": 14, - "SCL": 16, - "SDA": 13, - "SS": 17, - }, - "esp320": { - "LED": 5, - "MISO": 12, - "MOSI": 13, - "SCK": 14, - "SCL": 14, - "SDA": 2, - "SS": 15, - }, - "esp32cam": {}, - "esp32dev": {}, - "esp32doit-devkit-v1": {"LED": 2}, - "esp32thing": {"BUTTON": 0, "LED": 5, "SS": 2}, - "esp32vn-iot-uno": {}, - "espea32": {"BUTTON": 0, "LED": 5}, - "espectro32": {"LED": 15, "SD_SS": 33}, - "espino32": {"BUTTON": 0, "LED": 16}, - "featheresp32": { - "A0": 26, - "A1": 25, - "A10": 27, - "A11": 12, - "A12": 13, - "A13": 35, - "A2": 34, - "A4": 36, - "A5": 4, - "A6": 14, - "A7": 32, - "A8": 15, - "A9": 33, - "Ax": 2, - "LED": 13, - "MOSI": 18, - "RX": 16, - "SCK": 5, - "SDA": 23, - "SS": 33, - "TX": 17, - }, - "firebeetle32": {"LED": 2}, - "fm-devkit": { - "D0": 34, - "D1": 35, - "D10": 0, - "D2": 32, - "D3": 33, - "D4": 27, - "D5": 14, - "D6": 12, - "D7": 13, - "D8": 15, - "D9": 23, - "I2S_DOUT": 22, - "I2S_LRCLK": 25, - "I2S_MCLK": 2, - "I2S_SCLK": 26, - "LED": 5, - "SCL": 17, - "SDA": 16, - "SW1": 4, - "SW2": 18, - "SW3": 19, - "SW4": 21, - }, - "frogboard": {}, - "heltec_wifi_kit_32": { - "A1": 37, - "A2": 38, - "BUTTON": 0, - "LED": 25, - "RST_OLED": 16, - "SCL_OLED": 15, - "SDA_OLED": 4, - "Vext": 21, - }, - "heltec_wifi_lora_32": { - "BUTTON": 0, - "DIO0": 26, - "DIO1": 33, - "DIO2": 32, - "LED": 25, - "MOSI": 27, - "RST_LoRa": 14, - "RST_OLED": 16, - "SCK": 5, - "SCL_OLED": 15, - "SDA_OLED": 4, - "SS": 18, - "Vext": 21, - }, - "heltec_wifi_lora_32_V2": { - "BUTTON": 0, - "DIO0": 26, - "DIO1": 35, - "DIO2": 34, - "LED": 25, - "MOSI": 27, - "RST_LoRa": 14, - "RST_OLED": 16, - "SCK": 5, - "SCL_OLED": 15, - "SDA_OLED": 4, - "SS": 18, - "Vext": 21, - }, - "heltec_wireless_stick": { - "BUTTON": 0, - "DIO0": 26, - "DIO1": 35, - "DIO2": 34, - "LED": 25, - "MOSI": 27, - "RST_LoRa": 14, - "RST_OLED": 16, - "SCK": 5, - "SCL_OLED": 15, - "SDA_OLED": 4, - "SS": 18, - "Vext": 21, - }, - "hornbill32dev": {"BUTTON": 0, "LED": 13}, - "hornbill32minima": {"SS": 2}, - "intorobot": { - "A1": 39, - "A2": 35, - "A3": 25, - "A4": 26, - "A5": 14, - "A6": 12, - "A7": 15, - "A8": 13, - "A9": 2, - "BUTTON": 0, - "D0": 19, - "D1": 23, - "D2": 18, - "D3": 17, - "D4": 16, - "D5": 5, - "D6": 4, - "LED": 4, - "MISO": 17, - "MOSI": 16, - "RGB_B_BUILTIN": 22, - "RGB_G_BUILTIN": 21, - "RGB_R_BUILTIN": 27, - "SCL": 19, - "SDA": 23, - "T0": 19, - "T1": 23, - "T2": 18, - "T3": 17, - "T4": 16, - "T5": 5, - "T6": 4, - }, - "iotaap_magnolia": {}, - "iotbusio": {}, - "iotbusproteus": {}, - "lolin32": {"LED": 5}, - "lolin32-lite": {"LED": 22}, - "lolin_d32": {"LED": 5, "_VBAT": 35}, - "lolin_d32_pro": {"LED": 5, "_VBAT": 35}, - "lopy": { - "A1": 37, - "A2": 38, - "LED": 0, - "MISO": 37, - "MOSI": 22, - "SCK": 13, - "SCL": 13, - "SDA": 12, - "SS": 17, - }, - "lopy4": { - "A1": 37, - "A2": 38, - "LED": 0, - "MISO": 37, - "MOSI": 22, - "SCK": 13, - "SCL": 13, - "SDA": 12, - "SS": 18, - }, - "m5stack-core-esp32": { - "ADC1": 35, - "ADC2": 36, - "G0": 0, - "G1": 1, - "G12": 12, - "G13": 13, - "G15": 15, - "G16": 16, - "G17": 17, - "G18": 18, - "G19": 19, - "G2": 2, - "G21": 21, - "G22": 22, - "G23": 23, - "G25": 25, - "G26": 26, - "G3": 3, - "G34": 34, - "G35": 35, - "G36": 36, - "G5": 5, - "RXD2": 16, - "TXD2": 17, - }, - "m5stack-fire": { - "ADC1": 35, - "ADC2": 36, - "G0": 0, - "G1": 1, - "G12": 12, - "G13": 13, - "G15": 15, - "G16": 16, - "G17": 17, - "G18": 18, - "G19": 19, - "G2": 2, - "G21": 21, - "G22": 22, - "G23": 23, - "G25": 25, - "G26": 26, - "G3": 3, - "G34": 34, - "G35": 35, - "G36": 36, - "G5": 5, - }, - "m5stack-grey": { - "ADC1": 35, - "ADC2": 36, - "G0": 0, - "G1": 1, - "G12": 12, - "G13": 13, - "G15": 15, - "G16": 16, - "G17": 17, - "G18": 18, - "G19": 19, - "G2": 2, - "G21": 21, - "G22": 22, - "G23": 23, - "G25": 25, - "G26": 26, - "G3": 3, - "G34": 34, - "G35": 35, - "G36": 36, - "G5": 5, - "RXD2": 16, - "TXD2": 17, - }, - "m5stick-c": { - "ADC1": 35, - "ADC2": 36, - "G0": 0, - "G10": 10, - "G26": 26, - "G32": 32, - "G33": 33, - "G36": 36, - "G37": 37, - "G39": 39, - "G9": 9, - "MISO": 36, - "MOSI": 15, - "SCK": 13, - "SCL": 33, - "SDA": 32, - }, - "magicbit": { - "BLUE_LED": 17, - "BUZZER": 25, - "GREEN_LED": 16, - "LDR": 36, - "LED": 16, - "LEFT_BUTTON": 35, - "MOTOR1A": 27, - "MOTOR1B": 18, - "MOTOR2A": 16, - "MOTOR2B": 17, - "POT": 39, - "RED_LED": 27, - "RIGHT_PUTTON": 34, - "YELLOW_LED": 18, - }, - "mhetesp32devkit": {"LED": 2}, - "mhetesp32minikit": {"LED": 2}, - "microduino-core-esp32": { - "A0": 12, - "A1": 13, - "A10": 25, - "A11": 26, - "A12": 27, - "A13": 14, - "A2": 15, - "A3": 4, - "A6": 38, - "A7": 37, - "A8": 32, - "A9": 33, - "D0": 3, - "D1": 1, - "D10": 5, - "D11": 23, - "D12": 19, - "D13": 18, - "D14": 12, - "D15": 13, - "D16": 15, - "D17": 4, - "D18": 22, - "D19": 21, - "D2": 16, - "D20": 38, - "D21": 37, - "D3": 17, - "D4": 32, - "D5": 33, - "D6": 25, - "D7": 26, - "D8": 27, - "D9": 14, - "SCL": 21, - "SCL1": 13, - "SDA": 22, - "SDA1": 12, - }, - "nano32": {"BUTTON": 0, "LED": 16}, - "nina_w10": { - "D0": 3, - "D1": 1, - "D10": 5, - "D11": 19, - "D12": 23, - "D13": 18, - "D14": 13, - "D15": 12, - "D16": 32, - "D17": 33, - "D18": 21, - "D19": 34, - "D2": 26, - "D20": 36, - "D21": 39, - "D3": 25, - "D4": 35, - "D5": 27, - "D6": 22, - "D7": 0, - "D8": 15, - "D9": 14, - "LED_BLUE": 21, - "LED_GREEN": 33, - "LED_RED": 23, - "SCL": 13, - "SDA": 12, - "SW1": 33, - "SW2": 27, - }, - "node32s": {}, - "nodemcu-32s": {"BUTTON": 0, "LED": 2}, - "odroid_esp32": {"ADC1": 35, "ADC2": 36, "LED": 2, "SCL": 4, "SDA": 15, "SS": 22}, - "onehorse32dev": {"A1": 37, "A2": 38, "BUTTON": 0, "LED": 5}, - "oroca_edubot": { - "A0": 34, - "A1": 39, - "A2": 36, - "A3": 33, - "D0": 4, - "D1": 16, - "D2": 17, - "D3": 22, - "D4": 23, - "D5": 5, - "D6": 18, - "D7": 19, - "D8": 33, - "LED": 13, - "MOSI": 18, - "RX": 16, - "SCK": 5, - "SDA": 23, - "SS": 2, - "TX": 17, - "VBAT": 35, - }, - "pico32": {}, - "pocket_32": {"LED": 16}, - "pycom_gpy": { - "A1": 37, - "A2": 38, - "LED": 0, - "MISO": 37, - "MOSI": 22, - "SCK": 13, - "SCL": 13, - "SDA": 12, - "SS": 17, - }, - "quantum": {}, - "sparkfun_lora_gateway_1-channel": {"MISO": 12, "MOSI": 13, "SCK": 14, "SS": 16}, - "tinypico": {}, - "ttgo-lora32-v1": { - "A1": 37, - "A2": 38, - "BUTTON": 0, - "LED": 2, - "MOSI": 27, - "SCK": 5, - "SS": 18, - }, - "ttgo-t-beam": {"BUTTON": 39, "LED": 14, "MOSI": 27, "SCK": 5, "SS": 18}, - "ttgo-t-watch": {"BUTTON": 36, "MISO": 2, "MOSI": 15, "SCK": 14, "SS": 13}, - "ttgo-t1": {"LED": 22, "MISO": 2, "MOSI": 15, "SCK": 14, "SCL": 23, "SS": 13}, - "ttgo-t7-v13-mini32": {"LED": 22}, - "ttgo-t7-v14-mini32": {"LED": 19}, - "turta_iot_node": {}, - "vintlabs-devkit-v1": { - "LED": 2, - "PWM0": 12, - "PWM1": 13, - "PWM2": 14, - "PWM3": 15, - "PWM4": 16, - "PWM5": 17, - "PWM6": 18, - "PWM7": 19, - }, - "wemos_d1_mini32": { - "D0": 26, - "D1": 22, - "D2": 21, - "D3": 17, - "D4": 16, - "D5": 18, - "D6": 19, - "D7": 23, - "D8": 5, - "LED": 2, - "RXD": 3, - "TXD": 1, - "_VBAT": 35, - }, - "wemosbat": {"LED": 16}, - "wesp32": {"MISO": 32, "SCL": 4, "SDA": 15}, - "widora-air": { - "A1": 39, - "A2": 35, - "A3": 25, - "A4": 26, - "A5": 14, - "A6": 12, - "A7": 15, - "A8": 13, - "A9": 2, - "BUTTON": 0, - "D0": 19, - "D1": 23, - "D2": 18, - "D3": 17, - "D4": 16, - "D5": 5, - "D6": 4, - "LED": 25, - "MISO": 17, - "MOSI": 16, - "SCL": 19, - "SDA": 23, - "T0": 19, - "T1": 23, - "T2": 18, - "T3": 17, - "T4": 16, - "T5": 5, - "T6": 4, - }, - "xinabox_cw02": {"LED": 27}, -} - def _lookup_pin(value): if CORE.is_esp8266: - board_pins_dict = ESP8266_BOARD_PINS - base_pins = ESP8266_BASE_PINS + board_pins_dict = boards.ESP8266_BOARD_PINS + base_pins = boards.ESP8266_BASE_PINS elif CORE.is_esp32: - board_pins_dict = ESP32_BOARD_PINS - base_pins = ESP32_BASE_PINS + if CORE.board in boards.ESP32_C3_BOARD_PINS: + board_pins_dict = boards.ESP32_C3_BOARD_PINS + base_pins = boards.ESP32_C3_BASE_PINS + else: + board_pins_dict = boards.ESP32_BOARD_PINS + base_pins = boards.ESP32_BASE_PINS else: raise NotImplementedError @@ -915,9 +60,27 @@ _ESP_SDIO_PINS = { 11: "Flash Command", } +_ESP32C3_SDIO_PINS = { + 12: "Flash IO3/HOLD#", + 13: "Flash IO2/WP#", + 14: "Flash CS#", + 15: "Flash CLK", + 16: "Flash IO0/DI", + 17: "Flash IO1/DO", +} + def validate_gpio_pin(value): value = _translate_pin(value) + if CORE.is_esp32_c3: + if value < 0 or value > 22: + raise cv.Invalid(f"ESP32-C3: Invalid pin number: {value}") + if value in _ESP32C3_SDIO_PINS: + raise cv.Invalid( + "This pin cannot be used on ESP32-C3s and is already used by " + "the flash interface (function: {})".format(_ESP_SDIO_PINS[value]) + ) + return value if CORE.is_esp32: if value < 0 or value > 39: raise cv.Invalid(f"ESP32: Invalid pin number: {value}") @@ -995,6 +158,10 @@ def output_pin(value): def analog_pin(value): value = validate_gpio_pin(value) if CORE.is_esp32: + if CORE.is_esp32_c3: + if 0 <= value <= 4: # ADC1 + return value + raise cv.Invalid("ESP32-C3: Only pins 0 though 4 support ADC.") if 32 <= value <= 39: # ADC1 return value raise cv.Invalid("ESP32: Only pins 32 though 39 support ADC.") @@ -1104,7 +271,7 @@ def shorthand_input_pullup_pin(value): def shorthand_analog_pin(value): value = analog_pin(value) - return GPIO_FULL_INPUT_PIN_SCHEMA({CONF_NUMBER: value}) + return GPIO_FULL_ANALOG_PIN_SCHEMA({CONF_NUMBER: value}) def validate_has_interrupt(value): diff --git a/esphome/platformio_api.py b/esphome/platformio_api.py index 87ca12c9a8..3d7e9a6d48 100644 --- a/esphome/platformio_api.py +++ b/esphome/platformio_api.py @@ -202,6 +202,8 @@ STACKTRACE_ESP8266_PC_RE = re.compile(r"epc1=0x(4[0-9a-fA-F]{7})") STACKTRACE_ESP8266_EXCVADDR_RE = re.compile(r"excvaddr=0x(4[0-9a-fA-F]{7})") STACKTRACE_ESP32_PC_RE = re.compile(r"PC\s*:\s*(?:0x)?(4[0-9a-fA-F]{7})") STACKTRACE_ESP32_EXCVADDR_RE = re.compile(r"EXCVADDR\s*:\s*(?:0x)?(4[0-9a-fA-F]{7})") +STACKTRACE_ESP32_C3_PC_RE = re.compile(r"MEPC\s*:\s*(?:0x)?(4[0-9a-fA-F]{7})") +STACKTRACE_ESP32_C3_RA_RE = re.compile(r"RA\s*:\s*(?:0x)?(4[0-9a-fA-F]{7})") STACKTRACE_BAD_ALLOC_RE = re.compile( r"^last failed alloc call: (4[0-9a-fA-F]{7})\((\d+)\)$" ) @@ -228,6 +230,9 @@ def process_stacktrace(config, line, backtrace_state): # ESP32 PC/EXCVADDR _parse_register(config, STACKTRACE_ESP32_PC_RE, line) _parse_register(config, STACKTRACE_ESP32_EXCVADDR_RE, line) + # ESP32-C3 PC/RA + _parse_register(config, STACKTRACE_ESP32_C3_PC_RE, line) + _parse_register(config, STACKTRACE_ESP32_C3_RA_RE, line) # bad alloc match = re.match(STACKTRACE_BAD_ALLOC_RE, line) diff --git a/esphome/storage_json.py b/esphome/storage_json.py index 6f81e0d96a..c0fbc6edf7 100644 --- a/esphome/storage_json.py +++ b/esphome/storage_json.py @@ -4,14 +4,13 @@ from datetime import datetime import json import logging import os +from typing import Any, Optional, List from esphome import const from esphome.core import CORE from esphome.helpers import write_file_if_changed -# pylint: disable=unused-import, wrong-import-order -from esphome.core import CoreType -from typing import Any, Optional, List +from esphome.types import CoreType _LOGGER = logging.getLogger(__name__) diff --git a/esphome/types.py b/esphome/types.py new file mode 100644 index 0000000000..6bbfb00ce6 --- /dev/null +++ b/esphome/types.py @@ -0,0 +1,18 @@ +"""This helper module tracks commonly used types in the esphome python codebase.""" +from typing import Dict, Union, List + +from esphome.core import ID, Lambda, EsphomeCore + +ConfigFragmentType = Union[ + str, + int, + float, + None, + Dict[Union[str, int], "ConfigFragmentType"], + List["ConfigFragmentType"], + ID, + Lambda, +] +ConfigType = Dict[str, ConfigFragmentType] +CoreType = EsphomeCore +ConfigPathType = Union[str, int] diff --git a/esphome/wizard.py b/esphome/wizard.py index 620ceb9b59..3f989dd93d 100644 --- a/esphome/wizard.py +++ b/esphome/wizard.py @@ -6,10 +6,11 @@ import unicodedata import voluptuous as vol import esphome.config_validation as cv -from esphome.helpers import color, get_bool_env, write_file +from esphome.helpers import get_bool_env, write_file +from esphome.log import color, Fore # pylint: disable=anomalous-backslash-in-string -from esphome.pins import ESP32_BOARD_PINS, ESP8266_BOARD_PINS +from esphome.boards import ESP32_BOARD_PINS, ESP8266_BOARD_PINS from esphome.storage_json import StorageJSON, ext_storage_path from esphome.util import safe_print from esphome.const import ALLOWED_NAME_CHARS, ENV_QUICKWIZARD @@ -48,17 +49,6 @@ BASE_CONFIG = """esphome: platform: {platform} board: {board} -wifi: - ssid: "{ssid}" - password: "{psk}" - - # Enable fallback hotspot (captive portal) in case wifi connection fails - ap: - ssid: "{fallback_name}" - password: "{fallback_psk}" - -captive_portal: - # Enable logging logger: @@ -82,12 +72,43 @@ def wizard_file(**kwargs): config = BASE_CONFIG.format(**kwargs) - if kwargs["password"]: - config += ' password: "{0}"\n\nota:\n password: "{0}"\n'.format( - kwargs["password"] + # Configure API + if "password" in kwargs: + config += ' password: "{0}"\n'.format(kwargs["password"]) + + # Configure OTA + config += "\nota:\n" + if "ota_password" in kwargs: + config += ' password: "{0}"'.format(kwargs["ota_password"]) + elif "password" in kwargs: + config += ' password: "{0}"'.format(kwargs["password"]) + + # Configuring wifi + config += "\n\nwifi:\n" + + if "ssid" in kwargs: + config += """ ssid: "{ssid}" + password: "{psk}" +""".format( + **kwargs ) else: - config += "\nota:\n" + config += """ # ssid: "My SSID" + # password: "mypassword" + + networks: +""" + + config += """ + # Enable fallback hotspot (captive portal) in case wifi connection fails + ap: + ssid: "{fallback_name}" + password: "{fallback_psk}" + +captive_portal: +""".format( + **kwargs + ) return config @@ -96,9 +117,9 @@ def wizard_write(path, **kwargs): name = kwargs["name"] board = kwargs["board"] - kwargs["ssid"] = sanitize_double_quotes(kwargs["ssid"]) - kwargs["psk"] = sanitize_double_quotes(kwargs["psk"]) - kwargs["password"] = sanitize_double_quotes(kwargs["password"]) + for key in ("ssid", "psk", "password", "ota_password"): + if key in kwargs: + kwargs[key] = sanitize_double_quotes(kwargs[key]) if "platform" not in kwargs: kwargs["platform"] = "ESP8266" if board in ESP8266_BOARD_PINS else "ESP32" @@ -148,13 +169,13 @@ def wizard(path): if not path.endswith(".yaml") and not path.endswith(".yml"): safe_print( "Please make your configuration file {} have the extension .yaml or .yml" - "".format(color("cyan", path)) + "".format(color(Fore.CYAN, path)) ) return 1 if os.path.exists(path): safe_print( "Uh oh, it seems like {} already exists, please delete that file first " - "or chose another configuration file.".format(color("cyan", path)) + "or chose another configuration file.".format(color(Fore.CYAN, path)) ) return 2 safe_print("Hi there!") @@ -171,7 +192,7 @@ def wizard(path): safe_print() safe_print_step(1, CORE_BIG) safe_print( - "First up, please choose a " + color("green", "name") + " for your node." + "First up, please choose a " + color(Fore.GREEN, "name") + " for your node." ) safe_print( "It should be a unique name that can be used to identify the device later." @@ -179,12 +200,12 @@ def wizard(path): sleep(1) safe_print( "For example, I like calling the node in my living room {}.".format( - color("bold_white", "livingroom") + color(Fore.BOLD_WHITE, "livingroom") ) ) safe_print() sleep(1) - name = input(color("bold_white", "(name): ")) + name = input(color(Fore.BOLD_WHITE, "(name): ")) while True: try: @@ -193,21 +214,21 @@ def wizard(path): except vol.Invalid: safe_print( color( - "red", + Fore.RED, f'Oh noes, "{name}" isn\'t a valid name. Names can only ' - f"include numbers, lower-case letters, underscores and " - f"hyphens.", + f"include numbers, lower-case letters and hyphens. ", ) ) - name = strip_accents(name).lower().replace(" ", "_") + name = strip_accents(name).lower().replace(" ", "-") + name = strip_accents(name).lower().replace("_", "-") name = "".join(c for c in name if c in ALLOWED_NAME_CHARS) safe_print( - 'Shall I use "{}" as the name instead?'.format(color("cyan", name)) + 'Shall I use "{}" as the name instead?'.format(color(Fore.CYAN, name)) ) sleep(0.5) name = default_input("(name [{}]): ", name) - safe_print('Great! Your node is now called "{}".'.format(color("cyan", name))) + safe_print('Great! Your node is now called "{}".'.format(color(Fore.CYAN, name))) sleep(1) safe_print_step(2, ESP_BIG) safe_print( @@ -216,16 +237,16 @@ def wizard(path): ) safe_print( "Are you using an " - + color("green", "ESP32") + + color(Fore.GREEN, "ESP32") + " or " - + color("green", "ESP8266") + + color(Fore.GREEN, "ESP8266") + " platform? (Choose ESP8266 for Sonoff devices)" ) while True: sleep(0.5) safe_print() safe_print("Please enter either ESP32 or ESP8266.") - platform = input(color("bold_white", "(ESP32/ESP8266): ")) + platform = input(color(Fore.BOLD_WHITE, "(ESP32/ESP8266): ")) try: platform = vol.All(vol.Upper, vol.Any("ESP32", "ESP8266"))(platform) break @@ -235,7 +256,7 @@ def wizard(path): '"{}". Please try again.'.format(platform) ) safe_print( - "Thanks! You've chosen {} as your platform.".format(color("cyan", platform)) + "Thanks! You've chosen {} as your platform.".format(color(Fore.CYAN, platform)) ) safe_print() sleep(1) @@ -250,37 +271,39 @@ def wizard(path): ) safe_print( - "Next, I need to know what " + color("green", "board") + " you're using." + "Next, I need to know what " + color(Fore.GREEN, "board") + " you're using." ) sleep(0.5) - safe_print("Please go to {} and choose a board.".format(color("green", board_link))) + safe_print( + "Please go to {} and choose a board.".format(color(Fore.GREEN, board_link)) + ) if platform == "ESP32": - safe_print("(Type " + color("green", "esp01_1m") + " for Sonoff devices)") + safe_print("(Type " + color(Fore.GREEN, "esp01_1m") + " for Sonoff devices)") safe_print() # Don't sleep because user needs to copy link if platform == "ESP32": - safe_print('For example "{}".'.format(color("bold_white", "nodemcu-32s"))) + safe_print('For example "{}".'.format(color(Fore.BOLD_WHITE, "nodemcu-32s"))) boards = list(ESP32_BOARD_PINS.keys()) else: - safe_print('For example "{}".'.format(color("bold_white", "nodemcuv2"))) + safe_print('For example "{}".'.format(color(Fore.BOLD_WHITE, "nodemcuv2"))) boards = list(ESP8266_BOARD_PINS.keys()) safe_print("Options: {}".format(", ".join(sorted(boards)))) while True: - board = input(color("bold_white", "(board): ")) + board = input(color(Fore.BOLD_WHITE, "(board): ")) try: board = vol.All(vol.Lower, vol.Any(*boards))(board) break except vol.Invalid: safe_print( - color("red", f'Sorry, I don\'t think the board "{board}" exists.') + color(Fore.RED, f'Sorry, I don\'t think the board "{board}" exists.') ) safe_print() sleep(0.25) safe_print() safe_print( - "Way to go! You've chosen {} as your board.".format(color("cyan", board)) + "Way to go! You've chosen {} as your board.".format(color(Fore.CYAN, board)) ) safe_print() sleep(1) @@ -291,20 +314,20 @@ def wizard(path): sleep(1) safe_print( "First, what's the " - + color("green", "SSID") - + f" (the name) of the WiFi network {name} I should connect to?" + + color(Fore.GREEN, "SSID") + + f" (the name) of the WiFi network {name} should connect to?" ) sleep(1.5) - safe_print('For example "{}".'.format(color("bold_white", "Abraham Linksys"))) + safe_print('For example "{}".'.format(color(Fore.BOLD_WHITE, "Abraham Linksys"))) while True: - ssid = input(color("bold_white", "(ssid): ")) + ssid = input(color(Fore.BOLD_WHITE, "(ssid): ")) try: ssid = cv.ssid(ssid) break except vol.Invalid: safe_print( color( - "red", + Fore.RED, 'Unfortunately, "{}" doesn\'t seem to be a valid SSID. ' "Please try again.".format(ssid), ) @@ -314,20 +337,20 @@ def wizard(path): safe_print( 'Thank you very much! You\'ve just chosen "{}" as your SSID.' - "".format(color("cyan", ssid)) + "".format(color(Fore.CYAN, ssid)) ) safe_print() sleep(0.75) safe_print( "Now please state the " - + color("green", "password") + + color(Fore.GREEN, "password") + " of the WiFi network so that I can connect to it (Leave empty for no password)" ) safe_print() - safe_print('For example "{}"'.format(color("bold_white", "PASSWORD42"))) + safe_print('For example "{}"'.format(color(Fore.BOLD_WHITE, "PASSWORD42"))) sleep(0.5) - psk = input(color("bold_white", "(PSK): ")) + psk = input(color(Fore.BOLD_WHITE, "(PSK): ")) safe_print( "Perfect! WiFi is now set up (you can create static IPs and so on later)." ) @@ -340,12 +363,12 @@ def wizard(path): ) safe_print( "This can be insecure if you do not trust the WiFi network. Do you want to set " - "a " + color("green", "password") + " for connecting to this ESP?" + "a " + color(Fore.GREEN, "password") + " for connecting to this ESP?" ) safe_print() sleep(0.25) safe_print("Press ENTER for no password") - password = input(color("bold_white", "(password): ")) + password = input(color(Fore.BOLD_WHITE, "(password): ")) wizard_write( path=path, @@ -359,8 +382,8 @@ def wizard(path): safe_print() safe_print( - color("cyan", "DONE! I've now written a new configuration file to ") - + color("bold_cyan", path) + color(Fore.CYAN, "DONE! I've now written a new configuration file to ") + + color(Fore.BOLD_CYAN, path) ) safe_print() safe_print("Next steps:") diff --git a/esphome/writer.py b/esphome/writer.py index ec772b5127..641ae9b3cc 100644 --- a/esphome/writer.py +++ b/esphome/writer.py @@ -1,6 +1,8 @@ import logging import os import re +from pathlib import Path +from typing import Dict from esphome.config import iter_components from esphome.const import ( @@ -23,7 +25,8 @@ from esphome.helpers import ( get_bool_env, ) from esphome.storage_json import StorageJSON, storage_path -from esphome.pins import ESP8266_FLASH_SIZES, ESP8266_LD_SCRIPTS +from esphome.boards import ESP8266_FLASH_SIZES, ESP8266_LD_SCRIPTS +from esphome import loader _LOGGER = logging.getLogger(__name__) @@ -69,9 +72,7 @@ upload_flags = """, ) -UPLOAD_SPEED_OVERRIDE = { - "esp210": 57600, -} +UPLOAD_SPEED_OVERRIDE = {"esp210": 57600} def get_flags(key): @@ -207,11 +208,12 @@ def gather_lib_deps(): return [x.as_lib_dep for x in CORE.libraries] -def gather_build_flags(): - build_flags = CORE.build_flags +def gather_build_flags(overrides): + build_flags = list(CORE.build_flags) + build_flags += [overrides] if isinstance(overrides, str) else overrides # avoid changing build flags order - return list(sorted(list(build_flags))) + return list(sorted(build_flags)) ESP32_LARGE_PARTITIONS_CSV = """\ @@ -225,8 +227,10 @@ spiffs, data, spiffs, 0x391000, 0x00F000 def get_ini_content(): + overrides = CORE.config[CONF_ESPHOME].get(CONF_PLATFORMIO_OPTIONS, {}) + lib_deps = gather_lib_deps() - build_flags = gather_build_flags() + build_flags = gather_build_flags(overrides.pop("build_flags", [])) data = { "platform": CORE.arduino_version, @@ -272,7 +276,7 @@ def get_ini_content(): # Ignore libraries that are not explicitly used, but may # be added by LDF # data['lib_ldf_mode'] = 'chain' - data.update(CORE.config[CONF_ESPHOME].get(CONF_PLATFORMIO_OPTIONS, {})) + data.update(overrides) content = f"[env:{CORE.name}]\n" content += format_ini(data) @@ -355,7 +359,7 @@ or use the custom_components folder. def copy_src_tree(): - source_files = {} + source_files: Dict[Path, loader.SourceFile] = {} for _, component, _ in iter_components(CORE.config): source_files.update(component.source_files) @@ -365,37 +369,40 @@ def copy_src_tree(): # Build #include list for esphome.h include_l = [] - for target, path in source_files_l: - if os.path.splitext(path)[1] in HEADER_FILE_EXTENSIONS: + for target, _ in source_files_l: + if target.suffix in HEADER_FILE_EXTENSIONS: include_l.append(f'#include "{target}"') include_l.append("") include_s = "\n".join(include_l) source_files_copy = source_files.copy() - source_files_copy.pop(DEFINES_H_TARGET) + ignore_targets = [Path(x) for x in (DEFINES_H_TARGET, VERSION_H_TARGET)] + for t in ignore_targets: + source_files_copy.pop(t) - for path in walk_files(CORE.relative_src_path("esphome")): - if os.path.splitext(path)[1] not in SOURCE_FILE_EXTENSIONS: + for fname in walk_files(CORE.relative_src_path("esphome")): + p = Path(fname) + if p.suffix not in SOURCE_FILE_EXTENSIONS: # Not a source file, ignore continue # Transform path to target path name - target = os.path.relpath(path, CORE.relative_src_path()).replace( - os.path.sep, "/" - ) - if target in (DEFINES_H_TARGET, VERSION_H_TARGET): + target = p.relative_to(CORE.relative_src_path()) + if target in ignore_targets: # Ignore defines.h, will be dealt with later continue if target not in source_files_copy: # Source file removed, delete target - os.remove(path) + p.unlink() else: - src_path = source_files_copy.pop(target) - copy_file_if_changed(src_path, path) + src_file = source_files_copy.pop(target) + with src_file.path() as src_path: + copy_file_if_changed(src_path, p) # Now copy new files - for target, src_path in source_files_copy.items(): - dst_path = CORE.relative_src_path(*target.split("/")) - copy_file_if_changed(src_path, dst_path) + for target, src_file in source_files_copy.items(): + dst_path = CORE.relative_src_path(*target.parts) + with src_file.path() as src_path: + copy_file_if_changed(src_path, dst_path) # Finally copy defines write_file_if_changed( diff --git a/esphome/yaml_util.py b/esphome/yaml_util.py index f98bb272b8..10417e6de4 100644 --- a/esphome/yaml_util.py +++ b/esphome/yaml_util.py @@ -411,17 +411,19 @@ class ESPHomeDumper(yaml.SafeDumper): # pylint: disable=too-many-ancestors return self.represent_secret(value) return self.represent_scalar(tag="tag:yaml.org,2002:str", value=str(value)) - # pylint: disable=arguments-differ + # pylint: disable=arguments-renamed def represent_bool(self, value): return self.represent_scalar( "tag:yaml.org,2002:bool", "true" if value else "false" ) + # pylint: disable=arguments-renamed def represent_int(self, value): if is_secret(value): return self.represent_secret(value) return self.represent_scalar(tag="tag:yaml.org,2002:int", value=str(value)) + # pylint: disable=arguments-renamed def represent_float(self, value): if is_secret(value): return self.represent_secret(value) diff --git a/platformio.ini b/platformio.ini index 557803bb29..e1fb845358 100644 --- a/platformio.ini +++ b/platformio.ini @@ -10,43 +10,49 @@ include_dir = include [common] lib_deps = - AsyncTCP-esphome@1.1.1 AsyncMqttClient-esphome@0.8.4 ArduinoJson-esphomelib@5.13.3 - ESPAsyncWebServer-esphome@1.2.7 + esphome/ESPAsyncWebServer-esphome@1.3.0 FastLED@3.3.2 - NeoPixelBus-esphome@2.5.7 - ESPAsyncTCP-esphome@1.2.3 + NeoPixelBus-esphome@2.6.2 1655@1.0.2 ; TinyGPSPlus (has name conflict) 6865@1.0.0 ; TM1651 Battery Display 6306@1.0.3 ; HM3301 build_flags = - -Wno-reorder - -DUSE_WEB_SERVER - -DUSE_FAST_LED_LIGHT - -DUSE_NEO_PIXEL_BUS_LIGHT + -fno-exceptions + -Wno-sign-compare + -Wno-unused-but-set-variable + -Wno-unused-variable -DCLANG_TIDY -DESPHOME_LOG_LEVEL=ESPHOME_LOG_LEVEL_VERY_VERBOSE -; Don't use FlashStringHelper for debug builds because CLion freaks out for all -; log messages -src_filter = + +src_filter = + + + + [env:livingroom8266] -; use Arduino framework v2.3.0 for clang-tidy (latest 2.5.2 breaks static code analysis, see #760) -platform = espressif8266@1.8.0 -board = nodemcuv2 +; use Arduino framework v2.4.2 for clang-tidy (latest 2.5.2 breaks static code analysis, see #760) +platform = platformio/espressif8266@1.8.0 framework = arduino +board = nodemcuv2 lib_deps = ${common.lib_deps} ESP8266WiFi - Hash + ESPAsyncTCP-esphome@1.2.3 + Update build_flags = ${common.build_flags} -src_filter = ${common.src_filter} + +src_filter = ${common.src_filter} [env:livingroom32] -platform = espressif32@1.12.4 -board = nodemcu-32s +platform = platformio/espressif32@3.2.0 framework = arduino -lib_deps = ${common.lib_deps} -build_flags = ${common.build_flags} -DUSE_ETHERNET -src_filter = ${common.src_filter} + +board = nodemcu-32s +lib_deps = + ${common.lib_deps} + esphome/AsyncTCP-esphome@1.2.2 + Update +build_flags = + ${common.build_flags} + -DUSE_ETHERNET +src_filter = + ${common.src_filter} + - diff --git a/requirements.txt b/requirements.txt index be7c46f98b..b4d557f06e 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,14 +1,14 @@ voluptuous==0.12.1 -PyYAML==5.3.1 +PyYAML==5.4.1 paho-mqtt==1.5.1 colorama==0.4.4 -colorlog==4.7.2 tornado==6.1 -protobuf==3.13.0 +protobuf==3.17.3 tzlocal==2.1 pytz==2021.1 pyserial==3.5 ifaddr==0.1.7 -platformio==5.1.0 -esptool==2.8 +platformio==5.1.1 +esptool==3.1 click==7.1.2 +esphome-dashboard==20210728.0 diff --git a/requirements_optional.txt b/requirements_optional.txt new file mode 100644 index 0000000000..2c73430109 --- /dev/null +++ b/requirements_optional.txt @@ -0,0 +1,2 @@ +pillow>4.0.0 +cryptography>=2.0.0,<4 diff --git a/requirements_test.txt b/requirements_test.txt index 10c838a424..684582bd4c 100644 --- a/requirements_test.txt +++ b/requirements_test.txt @@ -1,14 +1,13 @@ -pylint==2.7.2 -flake8==3.8.4 -black==20.8b1 -pillow>4.0.0 -cryptography>=2.0.0,<4 +pylint==2.9.6 +flake8==3.9.2 +black==21.7b0 pexpect==4.8.0 pre-commit # Unit tests -pytest==6.2.2 -pytest-cov==2.11.1 -pytest-mock==3.5.1 +pytest==6.2.4 +pytest-cov==2.12.1 +pytest-mock==3.6.1 +pytest-asyncio==0.15.1 asyncmock==0.4.2 -hypothesis==5.21.0 +hypothesis==5.49.0 diff --git a/script/api_protobuf/api_protobuf.py b/script/api_protobuf/api_protobuf.py old mode 100644 new mode 100755 index f86145df2f..56c3e8ccc8 --- a/script/api_protobuf/api_protobuf.py +++ b/script/api_protobuf/api_protobuf.py @@ -1,3 +1,4 @@ +#!/usr/bin/env python3 """Python 3 script to automatically generate C++ classes for ESPHome's native API. It's pretty crappy spaghetti code, but it works. @@ -40,7 +41,16 @@ d = descriptor.FileDescriptorSet.FromString(content) def indent_list(text, padding=" "): - return [padding + line for line in text.splitlines()] + lines = [] + for line in text.splitlines(): + if line == "": + p = "" + elif line.startswith("#ifdef") or line.startswith("#endif"): + p = "" + else: + p = padding + lines.append(p + line) + return lines def indent(text, padding=" "): @@ -103,7 +113,7 @@ class TypeInfo: @property def class_member(self) -> str: - return f"{self.cpp_type} {self.field_name}{{{self.default_value}}}; // NOLINT" + return f"{self.cpp_type} {self.field_name}{{{self.default_value}}};" @property def decode_varint_content(self) -> str: @@ -432,7 +442,7 @@ class SInt64Type(TypeInfo): decode_varint = "value.as_sint64()" encode_func = "encode_sin64" - def dump(self): + def dump(self, name): o = f'sprintf(buffer, "%ll", {name});\n' o += f"out.append(buffer);" return o @@ -514,10 +524,10 @@ class RepeatedTypeInfo(TypeInfo): @property def encode_content(self): - return f"""\ - for (auto {'' if self._ti_is_bool else '&'}it : this->{self.field_name}) {{ - buffer.{self._ti.encode_func}({self.number}, it, true); - }}""" + o = f"for (auto {'' if self._ti_is_bool else '&'}it : this->{self.field_name}) {{\n" + o += f" buffer.{self._ti.encode_func}({self.number}, it, true);\n" + o += f"}}" + return o @property def dump_content(self): @@ -536,12 +546,13 @@ def build_enum_type(desc): out += f" {v.name} = {v.number},\n" out += "};\n" - cpp = f"template<>\n" - cpp += f"const char *proto_enum_to_string(enums::{name} value) {{\n" + cpp = f"template<> const char *proto_enum_to_string(enums::{name} value) {{\n" cpp += f" switch (value) {{\n" for v in desc.value: - cpp += f' case enums::{v.name}: return "{v.name}";\n' - cpp += f' default: return "UNKNOWN";\n' + cpp += f" case enums::{v.name}:\n" + cpp += f' return "{v.name}";\n' + cpp += f" default:\n" + cpp += f' return "UNKNOWN";\n' cpp += f" }}\n" cpp += f"}}\n" @@ -620,21 +631,35 @@ def build_message_type(desc): prot = "bool decode_64bit(uint32_t field_id, Proto64bit value) override;" protected_content.insert(0, prot) - o = f"void {desc.name}::encode(ProtoWriteBuffer buffer) const {{\n" - o += indent("\n".join(encode)) + "\n" + o = f"void {desc.name}::encode(ProtoWriteBuffer buffer) const {{" + if encode: + if len(encode) == 1 and len(encode[0]) + len(o) + 3 < 120: + o += f" {encode[0]} " + else: + o += "\n" + o += indent("\n".join(encode)) + "\n" o += "}\n" cpp += o prot = "void encode(ProtoWriteBuffer buffer) const override;" public_content.append(prot) - o = f"void {desc.name}::dump_to(std::string &out) const {{\n" + o = f"void {desc.name}::dump_to(std::string &out) const {{" if dump: - o += f" char buffer[64];\n" - o += f' out.append("{desc.name} {{\\n");\n' - o += indent("\n".join(dump)) + "\n" - o += f' out.append("}}");\n' + if len(dump) == 1 and len(dump[0]) + len(o) + 3 < 120: + o += f" {dump[0]} " + else: + o += "\n" + o += f" char buffer[64];\n" + o += f' out.append("{desc.name} {{\\n");\n' + o += indent("\n".join(dump)) + "\n" + o += f' out.append("}}");\n' else: - o += f' out.append("{desc.name} {{}}");\n' + o2 = f'out.append("{desc.name} {{}}");' + if len(o) + len(o2) + 3 < 120: + o += f" {o2} " + else: + o += "\n" + o += f" {o2}\n" o += "}\n" cpp += o prot = "void dump_to(std::string &out) const override;" @@ -643,8 +668,11 @@ def build_message_type(desc): out = f"class {desc.name} : public ProtoMessage {{\n" out += " public:\n" out += indent("\n".join(public_content)) + "\n" + out += "\n" out += " protected:\n" - out += indent("\n".join(protected_content)) + "\n" + out += indent("\n".join(protected_content)) + if len(protected_content) > 0: + out += "\n" out += "};\n" return out, cpp @@ -794,7 +822,7 @@ cpp += """\ namespace esphome { namespace api { -static const char *TAG = "api.service"; +static const char *const TAG = "api.service"; """ @@ -814,13 +842,13 @@ cases.sort() hpp += " protected:\n" hpp += f" bool read_message(uint32_t msg_size, uint32_t msg_type, uint8_t *msg_data) override;\n" out = f"bool {class_name}::read_message(uint32_t msg_size, uint32_t msg_type, uint8_t *msg_data) {{\n" -out += f" switch(msg_type) {{\n" +out += f" switch (msg_type) {{\n" for i, case in cases: c = f"case {i}: {{\n" c += indent(case) + "\n" c += f"}}" out += indent(c, " ") + "\n" -out += " default: \n" +out += " default:\n" out += " return false;\n" out += " }\n" out += " return true;\n" diff --git a/script/build_codeowners.py b/script/build_codeowners.py index a1e8d69046..2ee7521b91 100755 --- a/script/build_codeowners.py +++ b/script/build_codeowners.py @@ -36,7 +36,7 @@ esphome/core/* @esphome/core parts = [BASE] -# Fake some diretory so that get_component works +# Fake some directory so that get_component works CORE.config_path = str(root) codeowners = defaultdict(list) diff --git a/script/build_jsonschema.py b/script/build_jsonschema.py index 2c9534b861..1ab0ffa015 100644 --- a/script/build_jsonschema.py +++ b/script/build_jsonschema.py @@ -1,5 +1,6 @@ #!/usr/bin/env python3 +from esphome.cpp_generator import MockObj import json import argparse import os @@ -54,7 +55,7 @@ def is_ref(jschema): def unref(jschema): - return definitions[jschema[JSC_REF][len("#/definitions/") :]] + return definitions.get(jschema[JSC_REF][len("#/definitions/") :]) def add_definition_array_or_single_object(ref): @@ -62,7 +63,7 @@ def add_definition_array_or_single_object(ref): def add_core(): - from esphome.core_config import CONFIG_SCHEMA + from esphome.core.config import CONFIG_SCHEMA base_props["esphome"] = get_jschema("esphome", CONFIG_SCHEMA.schema) @@ -104,8 +105,11 @@ def add_registry(registry_name, registry): for name in registry.keys(): schema = get_jschema(str(name), registry[name].schema, create_return_ref=False) if not schema: - schema = {"type": "string"} + schema = {"type": "null"} o_schema = {"type": "object", JSC_PROPERTIES: {name: schema}} + o_schema = create_ref( + registry_name + "-" + name, str(registry[name].schema) + "x", o_schema + ) validators.append(o_schema) definitions[registry_name] = {JSC_ANYOF: validators} @@ -134,7 +138,7 @@ def add_module_schemas(name, module): def get_dirs(): - from esphome.config import CORE_COMPONENTS_PATH + from esphome.loader import CORE_COMPONENTS_PATH dir_names = [ d @@ -146,7 +150,7 @@ def get_dirs(): def get_logger_tags(): - from esphome.config import CORE_COMPONENTS_PATH + from esphome.loader import CORE_COMPONENTS_PATH import glob pattern = re.compile(r'^static const char(\*\s|\s\*)TAG = "(\w.*)";', re.MULTILINE) @@ -241,7 +245,7 @@ def add_components(): elif c.config_schema is not None: # adds root components which are not platforms, e.g. api: logger: - if c.is_multi_conf: + if c.multi_conf: schema = get_jschema(domain, c.config_schema) schema = add_definition_array_or_single_object(schema) else: @@ -322,7 +326,6 @@ def get_entry(parent_key, vschema): elif str(vschema) in ejs.list_schemas: ref = get_jschema(parent_key, ejs.list_schemas[str(vschema)][0]) entry = {JSC_ANYOF: [ref, {"type": "array", "items": ref}]} - elif str(vschema) in ejs.typed_schemas: schema_types = [{"type": "object", "properties": {"type": {"type": "string"}}}] entry = {"allOf": schema_types} @@ -342,6 +345,22 @@ def get_entry(parent_key, vschema): entry = get_automation_schema(parent_key, inner_vschema) elif type == "maybe": entry = get_jschema(parent_key, inner_vschema) + elif type == "one_of": + entry = {"enum": list(inner_vschema)} + elif type == "enum": + entry = {"enum": list(inner_vschema.keys())} + elif type == "effects": + # Like list schema but subset from list. + subset_list = inner_vschema[0] + # get_jschema('strobex', registry['strobe'].schema) + registry_schemas = [] + for name in subset_list: + registry_schemas.append(get_ref("light.EFFECTS_REGISTRY-" + name)) + + entry = { + JSC_ANYOF: [{"type": "array", "items": {JSC_ANYOF: registry_schemas}}] + } + else: raise ValueError("Unknown extracted schema type") elif str(vschema).startswith(" #include #include @@ -7,7 +12,7 @@ using namespace esphome; void setup() { - App.pre_setup("livingroom", __DATE__ " " __TIME__); + App.pre_setup("livingroom", __DATE__ ", " __TIME__, false); auto *log = new logger::Logger(115200, 512, logger::UART_SELECTION_UART0); log->pre_setup(); App.register_component(log); @@ -19,10 +24,12 @@ void setup() { ap.set_password("password1"); wifi->add_sta(ap); - auto *ota = new ota::OTAComponent(8266); - ota->start_safe_mode(); + auto *ota = new ota::OTAComponent(); + ota->set_port(8266); - auto *gpio = new gpio::GPIOSwitch("GPIO Switch", new GPIOPin(8, OUTPUT)); + auto *gpio = new gpio::GPIOSwitch(); + gpio->set_name("GPIO Switch"); + gpio->set_pin(new GPIOPin(8, OUTPUT, false)); App.register_component(gpio); App.register_switch(gpio); diff --git a/tests/livingroom32.cpp b/tests/livingroom32.cpp deleted file mode 100644 index 7005ec95e0..0000000000 --- a/tests/livingroom32.cpp +++ /dev/null @@ -1,26 +0,0 @@ -#include - -using namespace esphome; - -void setup() { - App.set_name("livingroom32"); - App.init_log(); - - App.init_wifi("YOUR_SSID", "YOUR_PASSWORD"); - App.init_mqtt("MQTT_HOST", "USERNAME", "PASSWORD"); - App.init_ota()->start_safe_mode(); - - // LEDC is only available on ESP32! for the ESP8266, take a look at App.make_esp8266_pwm_output(). - auto *red = App.make_ledc_output(32); // on pin 32 - auto *green = App.make_ledc_output(33); - auto *blue = App.make_ledc_output(34); - App.make_rgb_light("Livingroom Light", red, green, blue); - - App.make_dht_sensor("Livingroom Temperature", "Livingroom Humidity", 12); - App.make_status_binary_sensor("Livingroom Node Status"); - App.make_restart_switch("Livingroom Restart"); - - App.setup(); -} - -void loop() { App.loop(); } diff --git a/tests/test1.yaml b/tests/test1.yaml index f5c3ab9b57..b25852c93d 100644 --- a/tests/test1.yaml +++ b/tests/test1.yaml @@ -6,8 +6,11 @@ substitutions: esphome: name: test1 + name_add_mac_suffix: true platform: ESP32 board: nodemcu-32s + platformio_options: + board_build.partitions: huge_app.csv on_boot: priority: 150.0 then: @@ -48,6 +51,12 @@ esphome: Content-Type: application/json body: 'Some data' verify_ssl: false + on_response: + then: + - logger.log: + format: 'Response status: %d' + args: + - status_code build_path: build/test1 packages: @@ -62,6 +71,7 @@ wifi: password: '' channel: 14 bssid: 'A1:63:95:47:D3:1D' + enable_mdns: true manual_ip: static_ip: 192.168.178.230 gateway: 192.168.178.1 @@ -70,7 +80,7 @@ wifi: dns2: 1.2.2.1 domain: .local reboot_timeout: 120s - power_save_mode: none + power_save_mode: light http_request: useragent: esphome/device @@ -187,6 +197,24 @@ ota: port: 3286 reboot_timeout: 2min num_attempts: 5 + on_state_change: + then: + lambda: >- + ESP_LOGD("ota", "State %d", state); + on_begin: + then: + logger.log: "OTA begin" + on_progress: + then: + lambda: >- + ESP_LOGD("ota", "Got progress %f", x); + on_end: + then: + logger.log: "OTA end" + on_error: + then: + lambda: >- + ESP_LOGD("ota", "Got error code %d", x); logger: baud_rate: 0 @@ -228,10 +256,19 @@ wled: adalight: -mcp3008: - - id: 'mcp3008_hub' - cs_pin: GPIO12 +esp32_ble_tracker: +ble_client: + - mac_address: AA:BB:CC:DD:EE:FF + id: ble_foo + - mac_address: 11:22:33:44:55:66 + id: ble_blah + on_connect: + then: + - switch.turn_on: ble1_status + on_disconnect: + then: + - switch.turn_on: ble1_status mcp23s08: - id: 'mcp23s08_hub' cs_pin: GPIO12 @@ -243,6 +280,21 @@ mcp23s17: deviceaddress: 1 sensor: + - platform: ble_client + ble_client_id: ble_foo + name: 'Green iTag btn' + service_uuid: 'ffe0' + characteristic_uuid: 'ffe1' + descriptor_uuid: 'ffe2' + notify: true + update_interval: never + lambda: |- + ESP_LOGD("main", "Length of data is %i", x.size()); + return x[0]; + on_notify: + then: + - lambda: |- + ESP_LOGD("green_btn", "Button was pressed, val%f", x); - platform: adc pin: A0 name: 'Living Room Brightness' @@ -381,7 +433,7 @@ sensor: retain: False availability: state_topic: livingroom/custom_state_topic - measurement_time: 31 + measurement_duration: 31 - platform: bme280 temperature: name: 'Outside Temperature' @@ -480,6 +532,7 @@ sensor: voltage_divider: 2351 change_mode_every: 16 initial_mode: VOLTAGE + model: hlw8012 - platform: total_daily_energy power_id: hlw8012_power name: 'HLW8012 Total Daily Energy' @@ -625,6 +678,18 @@ sensor: falling_edge: DECREMENT internal_filter: 13us update_interval: 15s + - platform: pulse_meter + name: 'Pulse Meter' + id: pulse_meter_sensor + pin: GPIO12 + internal_filter: 100ms + timeout: 2 min + on_value: + - pulse_meter.set_total_pulses: + id: pulse_meter_sensor + value: 12345 + total: + name: 'Pulse Meter Total' - platform: rotary_encoder name: 'Rotary Encoder' id: rotary_encoder1 @@ -652,11 +717,6 @@ sensor: - platform: pulse_width name: Pulse Width pin: GPIO12 - - platform: senseair - uart_id: uart0 - co2: - name: 'SenseAir CO2 Value' - update_interval: 15s - platform: sm300d2 uart_id: uart0 co2: @@ -736,6 +796,13 @@ sensor: id: 'workshop_PMC_10_0' address: 0x69 update_interval: 10s + - platform: sht4x + temperature: + name: 'SHT4X Temperature' + humidity: + name: 'SHT4X Humidity' + address: 0x44 + update_interval: 15s - platform: shtcx temperature: name: 'Living Room Temperature 10' @@ -745,6 +812,7 @@ sensor: update_interval: 15s - platform: template name: 'Template Sensor' + state_class: measurement id: template_sensor lambda: |- if (id(ultrasonic_sensor1).state > 1) { @@ -842,25 +910,11 @@ sensor: name: 'AQI' calculation_type: 'CAQI' - platform: teleinfo - uart_id: uart0 - tags: - - tag_name: 'HCHC' - sensor: - name: 'hchc' - unit_of_measurement: 'Wh' - icon: mdi:flash - - tag_name: 'HCHP' - sensor: - name: 'hchp' - unit_of_measurement: 'Wh' - icon: mdi:flash - - tag_name: 'PAPP' - sensor: - name: 'papp' - unit_of_measurement: 'VA' - icon: mdi:flash - update_interval: 60s - historical_mode: true + tag_name: "HCHC" + name: "hchc" + unit_of_measurement: "Wh" + icon: mdi:flash + teleinfo_id: myteleinfo - platform: mcp9808 name: 'MCP9808 Temperature' update_interval: 15s @@ -868,12 +922,33 @@ sensor: id: ph_ezo address: 99 unit_of_measurement: 'pH' - - platform: mcp3008 + - platform: sdp3x + name: "HVAC Filter Pressure drop" + id: filter_pressure update_interval: 5s - mcp3008_id: 'mcp3008_hub' - id: freezer_temp_source - reference_voltage: 3.19 - number: 0 + accuracy_decimals: 3 + - platform: cs5460a + id: cs5460a1 + current: + name: "Socket current" + voltage: + name: "Mains voltage" + power: + name: "Socket power" + on_value: + then: + cs5460a.restart: cs5460a1 + samples: 1600 + pga_gain: 10X + current_gain: 0.01 + voltage_gain: 0.000573 + current_hpf: on + voltage_hpf: on + phase_offset: 20 + pulse_energy: 0.01 kWh + cs_pin: + mcp23xxx: mcp23017_hub + number: 14 esp32_touch: setup_mode: False @@ -985,10 +1060,6 @@ binary_sensor: pin: GPIO27 threshold: 1000 id: btn_left - - platform: nextion - page_id: 0 - component_id: 2 - name: 'Nextion Component 2 Touch' - platform: template name: 'Garage Door Open' id: garage_door @@ -1474,6 +1545,26 @@ climate: name: Toshiba Climate - platform: hitachi_ac344 name: Hitachi Climate + - platform: midea_ac + visual: + min_temperature: 18 °C + max_temperature: 25 °C + temperature_step: 0.1 °C + name: 'Electrolux EACS' + beeper: true + outdoor_temperature: + name: 'Temp' + power_usage: + name: 'Power' + humidity_setpoint: + name: 'Hum' + - platform: anova + name: Anova cooker + ble_client_id: ble_blah + +midea_dongle: + uart_id: uart0 + strength_icon: true switch: - platform: gpio @@ -1699,6 +1790,8 @@ switch: # Use pin number 0 number: 0 inverted: False + - platform: template + id: ble1_status fan: - platform: binary @@ -1708,13 +1801,10 @@ fan: direction_output: gpio_26 - platform: speed output: pca_6 + speed_count: 10 name: 'Living Room Fan 2' oscillation_output: gpio_19 direction_output: gpio_26 - speed: - low: 0.45 - medium: 0.75 - high: 1.0 oscillation_state_topic: oscillation/state/topic oscillation_command_topic: oscillation/command/topic speed_state_topic: speed/state/topic @@ -1737,6 +1827,13 @@ interval: btn_left_state = ((uint32_t) id(btn_left)->get_value() + 63 * (uint32_t)btn_left_state) >> 6; id(btn_left)->set_threshold(btn_left_state * 0.9); + - if: + condition: + display.is_displaying_page: + id: display1 + page_id: page1 + then: + - logger.log: 'Seeing page 1' color: - id: kbx_red @@ -1786,11 +1883,6 @@ display: intensity: 3 lambda: |- it.print("1234"); - - platform: nextion - uart_id: uart0 - lambda: |- - it.set_component_value("gauge", 50); - it.set_component_text("textview", "Hello World!"); - platform: pcd8544 cs_pin: GPIO23 dc_pin: GPIO23 @@ -1811,6 +1903,12 @@ display: - id: page2 lambda: |- // Nothing + on_page_change: + from: page1 + to: page2 + then: + lambda: |- + ESP_LOGD("display", "1 -> 2"); - platform: ssd1306_spi model: 'SSD1306 128x64' cs_pin: GPIO23 @@ -1865,24 +1963,6 @@ display: reset_pin: GPIO23 lambda: |- it.rectangle(0, 0, it.get_width(), it.get_height()); - - platform: waveshare_epaper - cs_pin: GPIO23 - dc_pin: GPIO23 - busy_pin: GPIO23 - reset_pin: GPIO23 - model: 2.90in - full_update_every: 30 - lambda: |- - it.rectangle(0, 0, it.get_width(), it.get_height()); - - platform: waveshare_epaper - cs_pin: GPIO23 - dc_pin: GPIO23 - busy_pin: GPIO23 - reset_pin: GPIO23 - model: 2.90inv2 - full_update_every: 30 - lambda: |- - it.rectangle(0, 0, it.get_width(), it.get_height()); - platform: st7789v cs_pin: GPIO5 dc_pin: GPIO16 @@ -1902,6 +1982,7 @@ display: row_start: 0 lambda: |- it.rectangle(0, 0, it.get_width(), it.get_height()); + tm1651: id: tm1651_battery clk_pin: GPIO23 @@ -1924,6 +2005,12 @@ pn532_spi: - mqtt.publish: topic: the/topic payload: !lambda 'return x;' + on_tag_removed: + - lambda: |- + ESP_LOGD("main", "Removed tag %s", x.c_str()); + - mqtt.publish: + topic: the/topic + payload: !lambda 'return x;' pn532_i2c: @@ -1938,10 +2025,15 @@ rc522_spi: ESP_LOGD("main", "Found tag %s", x.c_str()); rc522_i2c: - update_interval: 1s - on_tag: - - lambda: |- - ESP_LOGD("main", "Found tag %s", x.c_str()); + - update_interval: 1s + on_tag: + - lambda: |- + ESP_LOGD("main", "Found tag %s", x.c_str()); + + - update_interval: 1s + on_tag: + - lambda: |- + ESP_LOGD("main", "Found tag %s", x.c_str()); gps: uart_id: uart0 @@ -1988,6 +2080,17 @@ cover: debug: +tca9548a: + - address: 0x70 + id: multiplex0 + scan: True + - address: 0x71 + id: multiplex1 + scan: True + multiplexer: + id: multiplex0 + channel: 0 + pcf8574: - id: 'pcf8574_hub' address: 0x21 @@ -2068,6 +2171,10 @@ text_sensor: - platform: version name: 'ESPHome Version No Timestamp' hide_timestamp: True + - platform: teleinfo + tag_name: "OPTARIF" + name: "optarif" + teleinfo_id: myteleinfo sn74hc595: - id: 'sn74hc595_hub' @@ -2098,3 +2205,9 @@ canbus: lambda: 'return x[0] == 0x11;' then: light.toggle: ${roomname}_lights + +teleinfo: + id: myteleinfo + uart_id: uart0 + update_interval: 60s + historical_mode: true diff --git a/tests/test2.yaml b/tests/test2.yaml index 9826dc1346..9c99416133 100644 --- a/tests/test2.yaml +++ b/tests/test2.yaml @@ -14,6 +14,7 @@ ethernet: clk_mode: GPIO0_IN phy_addr: 0 power_pin: GPIO25 + enable_mdns: false manual_ip: static_ip: 192.168.178.56 gateway: 192.168.178.1 @@ -54,10 +55,18 @@ deep_sleep: as3935_i2c: irq_pin: GPIO12 +mcp3008: + - id: 'mcp3008_hub' + cs_pin: GPIO12 + sensor: - platform: homeassistant entity_id: sensor.hello_world id: ha_hello_world + - platform: homeassistant + entity_id: climate.living_room + attribute: temperature + id: ha_hello_world_temperature - platform: ble_rssi mac_address: AC:37:43:77:5F:4C name: 'BLE Google Home Mini RSSI value' @@ -70,6 +79,28 @@ sensor: - platform: ble_rssi service_uuid: '11223344-5566-7788-99aa-bbccddeeff00' name: 'BLE Test Service 128' + - platform: b_parasite + mac_address: F0:CA:F0:CA:01:01 + humidity: + name: 'b-parasite Air Humidity' + temperature: + name: 'b-parasite Air Temperature' + moisture: + name: 'b-parasite Soil Moisture' + battery_voltage: + name: 'b-parasite Battery Voltage' + - platform: senseair + id: senseair0 + co2: + name: 'SenseAir CO2 Value' + on_value: + then: + - senseair.background_calibration: senseair0 + - senseair.background_calibration_result: senseair0 + - senseair.abc_get_period: senseair0 + - senseair.abc_enable: senseair0 + - senseair.abc_disable: senseair0 + update_interval: 15s - platform: ruuvitag mac_address: FF:56:D3:2F:7D:E8 humidity: @@ -187,14 +218,24 @@ sensor: name: 'ATC Battery-Level' battery_voltage: name: 'ATC Battery-Voltage' + - platform: pvvx_mithermometer + mac_address: 'A4:C1:38:4E:16:78' + temperature: + name: 'PVVX Temperature' + humidity: + name: 'PVVX Humidity' + battery_level: + name: 'PVVX Battery-Level' + battery_voltage: + name: 'PVVX Battery-Voltage' - platform: inkbird_ibsth1_mini mac_address: 38:81:D7:0A:9C:11 temperature: - name: 'Inkbird IBS-TH1 Temperature' + name: 'Inkbird IBS-TH1 Temperature' humidity: - name: 'Inkbird IBS-TH1 Humidity' + name: 'Inkbird IBS-TH1 Humidity' battery_level: - name: 'Inkbird IBS-TH1 Battery Level' + name: 'Inkbird IBS-TH1 Battery Level' - platform: ltr390 uv: name: "LTR390 UV" @@ -208,8 +249,17 @@ sensor: resolution: 18 wfac: 1.0 address: 0x53 - update_interval: 60s - + update_interval: 60s + - platform: sgp40 + name: 'Workshop VOC' + update_interval: 5s + store_baseline: 'true' + - platform: mcp3008 + update_interval: 5s + mcp3008_id: 'mcp3008_hub' + id: freezer_temp_source + reference_voltage: 3.19 + number: 0 time: - platform: homeassistant on_time: @@ -224,6 +274,10 @@ binary_sensor: - platform: homeassistant entity_id: binary_sensor.hello_world id: ha_hello_world_binary + - platform: homeassistant + entity_id: binary_sensor.hello + attribute: world + id: ha_hello_world_binary_attribute - platform: ble_presence mac_address: AC:37:43:77:5F:4C name: 'ESP32 BLE Tracker Google Home Mini' @@ -324,6 +378,8 @@ text_sensor: - homeassistant.tag_scanned: 1234-abcd - deep_sleep.enter: sleep_duration: 30min + - deep_sleep.enter: + sleep_duration: !lambda "return 30 * 60 * 1000;" - platform: template name: 'Template Text Sensor' lambda: |- @@ -331,6 +387,10 @@ text_sensor: - platform: homeassistant entity_id: sensor.hello_world2 id: ha_hello_world2 + - platform: homeassistant + entity_id: sensor.hello_world3 + id: ha_hello_world3 + attribute: some_attribute - platform: ble_scanner name: Scanner diff --git a/tests/test3.yaml b/tests/test3.yaml index ee95e9d35f..d4c8e526fe 100644 --- a/tests/test3.yaml +++ b/tests/test3.yaml @@ -1,6 +1,6 @@ esphome: - name: $devicename - comment: $devicecomment + name: $device_name + comment: $device_comment platform: ESP8266 board: d1_mini build_path: build/test3 @@ -13,8 +13,8 @@ esphome: - custom.h substitutions: - devicename: test3 - devicecomment: test3 device + device_name: test3 + device_comment: test3 device min_sub: '0.03' max_sub: '12.0%' @@ -141,6 +141,14 @@ api: then: - dfplayer.random + - service: dfplayer_volume_up + then: + - dfplayer.volume_up + + - service: dfplayer_volume_down + then: + - dfplayer.volume_down + - service: battery_level_percent variables: level_percent: int @@ -177,6 +185,26 @@ api: kp: 1.0 kd: 1.0 ki: 1.0 + - service: fingerprint_grow_enroll + variables: + finger_id: int + num_scans: int + then: + - fingerprint_grow.enroll: + finger_id: !lambda 'return finger_id;' + num_scans: !lambda 'return num_scans;' + - service: fingerprint_grow_cancel_enroll + then: + - fingerprint_grow.cancel_enroll: + - service: fingerprint_grow_delete + variables: + finger_id: int + then: + - fingerprint_grow.delete: + finger_id: !lambda 'return finger_id;' + - service: fingerprint_grow_delete_all + then: + - fingerprint_grow.delete_all: wifi: ssid: 'MySSID' @@ -193,9 +221,33 @@ spi: miso_pin: GPIO14 uart: - - tx_pin: GPIO1 + - id: uart1 + tx_pin: GPIO1 rx_pin: GPIO3 baud_rate: 115200 + - id: uart2 + tx_pin: GPIO4 + rx_pin: GPIO5 + baud_rate: 9600 + - id: uart3 + tx_pin: GPIO4 + rx_pin: GPIO5 + baud_rate: 4800 + - id: uart4 + tx_pin: GPIO4 + rx_pin: GPIO5 + baud_rate: 9600 + - id: uart5 + tx_pin: GPIO4 + rx_pin: GPIO5 + baud_rate: 9600 + - id: uart6 + tx_pin: GPIO4 + rx_pin: GPIO5 + baud_rate: 9600 + +modbus: + uart_id: uart1 ota: safe_mode: True @@ -217,6 +269,7 @@ wled: adalight: + sensor: - platform: apds9960 type: proximity @@ -349,6 +402,7 @@ sensor: active_power_b: name: ADE7953 Active Power B - platform: pzem004t + uart_id: uart3 voltage: name: 'PZEM00T Voltage' current: @@ -388,6 +442,7 @@ sensor: name: 'AQI' calculation_type: 'AQI' - platform: pmsx003 + uart_id: uart2 type: PMSX003 pm_1_0: name: 'PM 1.0 Concentration' @@ -395,7 +450,26 @@ sensor: name: 'PM 2.5 Concentration' pm_10_0: name: 'PM 10.0 Concentration' + pm_1_0_std: + name: 'PM 1.0 Standard Atmospher Concentration' + pm_2_5_std: + name: 'PM 2.5 Standard Atmospher Concentration' + pm_10_0_std: + name: 'PM 10.0 Standard Atmospher Concentration' + pm_0_3um: + name: 'Particulate Count >0.3um' + pm_0_5um: + name: 'Particulate Count >0.5um' + pm_1_0um: + name: 'Particulate Count >1.0um' + pm_2_5um: + name: 'Particulate Count >2.5um' + pm_5_0um: + name: 'Particulate Count >5.0um' + pm_10_0um: + name: 'Particulate Count >10.0um' - platform: pmsx003 + uart_id: uart2 type: PMS5003T pm_2_5: name: 'PM 2.5 Concentration' @@ -404,9 +478,32 @@ sensor: humidity: name: 'PMS Humidity' - platform: pmsx003 + uart_id: uart2 type: PMS5003ST + pm_1_0: + name: 'PM 1.0 Concentration' pm_2_5: name: 'PM 2.5 Concentration' + pm_10_0: + name: 'PM 10.0 Concentration' + pm_1_0_std: + name: 'PM 1.0 Standard Atmospher Concentration' + pm_2_5_std: + name: 'PM 2.5 Standard Atmospher Concentration' + pm_10_0_std: + name: 'PM 10.0 Standard Atmospher Concentration' + pm_0_3um: + name: 'Particulate Count >0.3um' + pm_0_5um: + name: 'Particulate Count >0.5um' + pm_1_0um: + name: 'Particulate Count >1.0um' + pm_2_5um: + name: 'Particulate Count >2.5um' + pm_5_0um: + name: 'Particulate Count >5.0um' + pm_10_0um: + name: 'Particulate Count >10.0um' temperature: name: 'PMS Temperature' humidity: @@ -414,6 +511,7 @@ sensor: formaldehyde: name: 'PMS Formaldehyde Concentration' - platform: cse7766 + uart_id: uart3 voltage: name: 'CSE7766 Voltage' current: @@ -424,7 +522,88 @@ sensor: id: ph_ezo address: 99 unit_of_measurement: 'pH' + - platform: tof10120 + name: "Distance sensor" + update_interval: 5s + - platform: fingerprint_grow + fingerprint_count: + name: "Fingerprint Count" + status: + name: "Fingerprint Status" + capacity: + name: "Fingerprint Capacity" + security_level: + name: "Fingerprint Security Level" + last_finger_id: + name: "Fingerprint Last Finger ID" + last_confidence: + name: "Fingerprint Last Confidence" + - platform: sdm_meter + phase_a: + current: + name: 'Phase A Current' + voltage: + name: 'Phase A Voltage' + active_power: + name: 'Phase A Power' + power_factor: + name: 'Phase A Power Factor' + apparent_power: + name: 'Phase A Apparent Power' + reactive_power: + name: 'Phase A Reactive Power' + phase_angle: + name: 'Phase A Phase Angle' + phase_b: + current: + name: 'Phase B Current' + voltage: + name: 'Phase B Voltage' + active_power: + name: 'Phase B Power' + power_factor: + name: 'Phase B Power Factor' + apparent_power: + name: 'Phase B Apparent Power' + reactive_power: + name: 'Phase B Reactive Power' + phase_angle: + name: 'Phase B Phase Angle' + phase_c: + current: + name: 'Phase C Current' + voltage: + name: 'Phase C Voltage' + active_power: + name: 'Phase C Power' + power_factor: + name: 'Phase C Power Factor' + apparent_power: + name: 'Phase C Apparent Power' + reactive_power: + name: 'Phase C Reactive Power' + phase_angle: + name: 'Phase C Phase Angle' + frequency: + name: 'Frequency' + import_active_energy: + name: 'Import Active Energy' + export_active_energy: + name: 'Export Active Energy' + import_reactive_energy: + name: 'Import Reactive Energy' + export_reactive_energy: + name: 'Export Reactive Energy' + - platform: nextion + id: testnumber + name: 'testnumber' + variable_name: testnumber + - platform: nextion + id: testwave + name: 'testwave' + component_id: 2 + wave_channel_id: 1 time: - platform: homeassistant @@ -486,6 +665,8 @@ binary_sensor: - platform: ttp229_bsf channel: 1 name: TTP229 BSF Test + - platform: fingerprint_grow + name: "Fingerprint Enrolling" - platform: custom lambda: |- auto s = new CustomBinarySensor(); @@ -494,7 +675,14 @@ binary_sensor: binary_sensors: - id: custom_binary_sensor name: Custom Binary Sensor - + - platform: nextion + page_id: 0 + component_id: 2 + name: 'Nextion Component 2 Touch' + - platform: nextion + id: r0_sensor + name: 'R0 Sensor' + component_name: page0.r0 globals: - id: my_global_string type: std::string @@ -542,11 +730,31 @@ text_sensor: text_sensors: - id: custom_text_sensor name: Custom Text Sensor + - platform: nextion + name: text0 + id: text0 + update_interval: 4s + component_name: text0 script: - id: my_script then: - lambda: 'ESP_LOGD("main", "Hello World!");' + - id: climate_custom + then: + - climate.control: + id: midea_ac_unit + custom_preset: FREEZE_PROTECTION + custom_fan_mode: SILENT + - id: climate_preset + then: + - climate.control: + id: midea_ac_unit + preset: SLEEP + +sm2135: + data_pin: GPIO12 + clock_pin: GPIO14 switch: - platform: template @@ -578,6 +786,10 @@ switch: switches: - id: custom_switch name: Custom Switch + - platform: nextion + id: r0 + name: 'R0 Switch' + component_name: page0.r0 custom_component: lambda: |- @@ -704,6 +916,32 @@ climate: kp: 0.0 ki: 0.0 kd: 0.0 + - platform: midea_ac + id: midea_ac_unit + visual: + min_temperature: 18 °C + max_temperature: 25 °C + temperature_step: 0.1 °C + name: "Electrolux EACS" + beeper: true + custom_fan_modes: + - SILENT + - TURBO + preset_eco: true + preset_sleep: true + preset_boost: true + custom_presets: + - FREEZE_PROTECTION + outdoor_temperature: + name: "Temp" + power_usage: + name: "Power" + humidity_setpoint: + name: "Hum" + +midea_dongle: + uart_id: uart1 + strength_icon: true cover: - platform: endstop @@ -790,6 +1028,21 @@ output: pin: GPIO5 id: my_slow_pwm period: 15s + - platform: sm2135 + id: sm2135_0 + channel: 0 + - platform: sm2135 + id: sm2135_1 + channel: 1 + - platform: sm2135 + id: sm2135_2 + channel: 2 + - platform: sm2135 + id: sm2135_3 + channel: 3 + - platform: sm2135 + id: sm2135_4 + channel: 4 mcp23017: id: mcp23017_hub @@ -810,6 +1063,7 @@ light: effects: - wled: - adalight: + uart_id: uart3 - e131: universe: 1 - platform: hbridge @@ -831,6 +1085,7 @@ ttp229_bsf: scl_pin: D1 sim800l: + uart_id: uart4 on_sms_received: - lambda: |- std::string str; @@ -843,6 +1098,7 @@ sim800l: recipient: '+1234' dfplayer: + uart_id: uart5 on_finished_playback: then: if: @@ -856,6 +1112,7 @@ tm1651: dio_pin: D5 rf_bridge: + uart_id: uart5 on_code_received: - lambda: |- uint32_t test; @@ -915,7 +1172,48 @@ display: id: my_matrix lambda: |- it.printdigit("hello"); + - platform: nextion + uart_id: uart1 + tft_url: 'http://esphome.io/default35.tft' + update_interval: 5s + on_sleep: + then: + lambda: 'ESP_LOGD("display","Display went to sleep");' + on_wake: + then: + lambda: 'ESP_LOGD("display","Display woke up");' http_request: useragent: esphome/device timeout: 10s + +fingerprint_grow: + sensing_pin: 4 + password: 0x12FE37DC + new_password: 0xA65B9840 + on_finger_scan_matched: + - homeassistant.event: + event: esphome.${device_name}_fingerprint_grow_finger_scan_matched + data: + finger_id: !lambda 'return finger_id;' + confidence: !lambda 'return confidence;' + on_finger_scan_unmatched: + - homeassistant.event: + event: esphome.${device_name}_fingerprint_grow_finger_scan_unmatched + on_enrollment_scan: + - homeassistant.event: + event: esphome.${device_name}_fingerprint_grow_enrollment_scan + data: + finger_id: !lambda 'return finger_id;' + scan_num: !lambda 'return scan_num;' + on_enrollment_done: + - homeassistant.event: + event: esphome.${device_name}_fingerprint_grow_node_enrollment_done + data: + finger_id: !lambda 'return finger_id;' + on_enrollment_failed: + - homeassistant.event: + event: esphome.${device_name}_fingerprint_grow_enrollment_failed + data: + finger_id: !lambda 'return finger_id;' + uart_id: uart6 diff --git a/tests/test4.yaml b/tests/test4.yaml index bfeff01e93..2857e8ca5f 100644 --- a/tests/test4.yaml +++ b/tests/test4.yaml @@ -63,6 +63,15 @@ sensor: - platform: tuya id: tuya_sensor sensor_datapoint: 1 + - platform: "hrxl_maxsonar_wr" + name: "Rainwater Tank Level" + filters: + - sliding_window_moving_average: + window_size: 12 + send_every: 12 + - or: + - throttle: "20min" + - delta: 0.02 # # platform sensor.apds9960 requires component apds9960 # @@ -86,6 +95,29 @@ binary_sensor: - platform: tuya id: tuya_binary_sensor sensor_datapoint: 1 + - platform: template + id: ar1 + lambda: 'return {};' + filters: + - autorepeat: + - delay: 2s + time_off: 100ms + time_on: 900ms + - delay: 4s + time_off: 100ms + time_on: 400ms + on_state: + then: + - lambda: 'ESP_LOGI("ar1:", "%d", x);' + - platform: xpt2046 + xpt2046_id: touchscreen + id: touch_key0 + x_min: 80 + x_max: 160 + y_min: 106 + y_max: 212 + on_state: + - lambda: 'ESP_LOGI("main", "key0: %s", (x ? "touch" : "release"));' climate: - platform: tuya @@ -99,3 +131,124 @@ switch: - platform: tuya id: tuya_switch switch_datapoint: 1 + +light: + - platform: fastled_clockless + id: led_matrix_32x8 + name: "led_matrix_32x8" + chipset: WS2812B + pin: GPIO15 + num_leds: 256 + rgb_order: GRB + default_transition_length: 0s + color_correct: [50%, 50%, 50%] + - platform: tuya + id: tuya_light + switch_datapoint: 1 + dimmer_datapoint: 2 + min_value_datapoint: 3 + color_temperature_datapoint: 4 + min_value: 1 + max_value: 100 + cold_white_color_temperature: 153 mireds + warm_white_color_temperature: 500 mireds + gamma_correct: 1 + +display: + - platform: addressable_light + id: led_matrix_32x8_display + addressable_light_id: led_matrix_32x8 + width: 32 + height: 8 + pixel_mapper: |- + if (x % 2 == 0) { + return (x * 8) + y; + } + return (x * 8) + (7 - y); + lambda: |- + Color red = Color(0xFF0000); + Color green = Color(0x00FF00); + Color blue = Color(0x0000FF); + it.rectangle(0, 0, it.get_width(), it.get_height(), red); + it.rectangle(1, 1, it.get_width()-2, it.get_height()-2, green); + it.rectangle(2, 2, it.get_width()-4, it.get_height()-4, blue); + it.rectangle(3, 3, it.get_width()-6, it.get_height()-6, red); + rotation: 0° + update_interval: 16ms + + - platform: waveshare_epaper + cs_pin: GPIO23 + dc_pin: GPIO23 + busy_pin: GPIO23 + reset_pin: GPIO23 + model: 2.13in-ttgo-b1 + full_update_every: 30 + lambda: |- + it.rectangle(0, 0, it.get_width(), it.get_height()); + - platform: waveshare_epaper + cs_pin: GPIO23 + dc_pin: GPIO23 + busy_pin: GPIO23 + reset_pin: GPIO23 + model: 2.90in + full_update_every: 30 + lambda: |- + it.rectangle(0, 0, it.get_width(), it.get_height()); + - platform: waveshare_epaper + cs_pin: GPIO23 + dc_pin: GPIO23 + busy_pin: GPIO23 + reset_pin: GPIO23 + model: 2.90inv2 + full_update_every: 30 + lambda: |- + it.rectangle(0, 0, it.get_width(), it.get_height()); + +esp32_camera: + name: ESP-32 Camera + data_pins: [GPIO17, GPIO35, GPIO34, GPIO5, GPIO39, GPIO18, GPIO36, GPIO19] + vsync_pin: GPIO22 + href_pin: GPIO26 + pixel_clock_pin: GPIO21 + external_clock: + pin: GPIO27 + frequency: 20MHz + i2c_pins: + sda: GPIO25 + scl: GPIO23 + reset_pin: GPIO15 + power_down_pin: GPIO1 + resolution: 640x480 + jpeg_quality: 10 + +external_components: + - source: github://esphome/esphome@dev + refresh: 1d + components: ["bh1750"] + - source: ../esphome/components + components: ["sntp"] +xpt2046: + id: touchscreen + cs_pin: 17 + irq_pin: 16 + update_interval: 50ms + report_interval: 1s + threshold: 400 + dimension_x: 240 + dimension_y: 320 + calibration_x_min: 3860 + calibration_x_max: 280 + calibration_y_min: 340 + calibration_y_max: 3860 + swap_x_y: False + on_state: + - lambda: |- + ESP_LOGI("main", "args x=%d, y=%d, touched=%s", x, y, (touched ? "touch" : "release")); + ESP_LOGI("main", "member x=%d, y=%d, touched=%d, x_raw=%d, y_raw=%d, z_raw=%d", + id(touchscreen).x, + id(touchscreen).y, + (int) id(touchscreen).touched, + id(touchscreen).x_raw, + id(touchscreen).y_raw, + id(touchscreen).z_raw + ); diff --git a/tests/test5.yaml b/tests/test5.yaml new file mode 100644 index 0000000000..6ccb83a11a --- /dev/null +++ b/tests/test5.yaml @@ -0,0 +1,148 @@ +esphome: + name: test5 + platform: ESP32 + board: nodemcu-32s + build_path: build/test5 + project: + name: esphome.test5_project + version: "1.0.0" + +wifi: + networks: + - ssid: 'MySSID' + password: 'password1' + +api: + +ota: + +logger: + +uart: + - id: uart1 + tx_pin: 1 + rx_pin: 3 + baud_rate: 9600 + - id: uart2 + tx_pin: 17 + rx_pin: 16 + baud_rate: 19200 + + +modbus: + uart_id: uart1 + +binary_sensor: + - platform: gpio + pin: GPIO0 + id: io0_button + +tlc5947: + data_pin: GPIO12 + clock_pin: GPIO14 + lat_pin: GPIO15 + +output: + - platform: gpio + pin: GPIO2 + id: built_in_led + + - platform: tlc5947 + id: output_red + channel: 0 + max_power: 0.8 + +demo: + +esp32_ble: + +esp32_ble_server: + manufacturer: "ESPHome" + model: "Test5" + +esp32_improv: + authorizer: io0_button + authorized_duration: 1min + status_indicator: built_in_led + +number: + - platform: template + name: My template number + id: template_number_id + optimistic: true + on_value: + - logger.log: + format: "Number changed to %f" + args: ["x"] + set_action: + - logger.log: + format: "Template Number set to %f" + args: ["x"] + max_value: 100 + min_value: 0 + step: 5 + +select: + - platform: template + name: My template select + id: template_select_id + optimistic: true + initial_option: two + restore_value: true + on_value: + - logger.log: + format: "Select changed to %s" + args: ["x.c_str()"] + set_action: + - logger.log: + format: "Template Select set to %s" + args: ["x.c_str()"] + - select.set: + id: template_select_id + option: two + options: + - one + - two + - three + +sensor: + - platform: selec_meter + total_active_energy: + name: "SelecEM2M Total Active Energy" + import_active_energy: + name: "SelecEM2M Import Active Energy" + export_active_energy: + name: "SelecEM2M Export Active Energy" + total_reactive_energy: + name: "SelecEM2M Total Reactive Energy" + import_reactive_energy: + name: "SelecEM2M Import Reactive Energy" + export_reactive_energy: + name: "SelecEM2M Export Reactive Energy" + apparent_energy: + name: "SelecEM2M Apparent Energy" + active_power: + name: "SelecEM2M Active Power" + reactive_power: + name: "SelecEM2M Reactive Power" + apparent_power: + name: "SelecEM2M Apparent Power" + voltage: + name: "SelecEM2M Voltage" + current: + name: "SelecEM2M Current" + power_factor: + name: "SelecEM2M Power Factor" + frequency: + name: "SelecEM2M Frequency" + maximum_demand_active_power: + name: "SelecEM2M Maximum Demand Active Power" + maximum_demand_reactive_power: + name: "SelecEM2M Maximum Demand Reactive Power" + maximum_demand_apparent_power: + name: "SelecEM2M Maximum Demand Apparent Power" + + - platform: t6615 + uart_id: uart2 + co2: + name: CO2 Sensor diff --git a/tests/unit_tests/strategies.py b/tests/unit_tests/strategies.py index 4bc0482f5f..30768f9d56 100644 --- a/tests/unit_tests/strategies.py +++ b/tests/unit_tests/strategies.py @@ -4,7 +4,7 @@ import hypothesis.strategies._internal.core as st from hypothesis.strategies._internal.strategies import SearchStrategy -@st.defines_strategy_with_reusable_values +@st.defines_strategy(force_reusable_values=True) def mac_addr_strings(): # type: () -> SearchStrategy[Text] """A strategy for MAC address strings. diff --git a/tests/unit_tests/test_config_validation.py b/tests/unit_tests/test_config_validation.py index 949d4251ee..16cfb16e94 100644 --- a/tests/unit_tests/test_config_validation.py +++ b/tests/unit_tests/test_config_validation.py @@ -27,7 +27,7 @@ def test_alphanumeric__invalid(value): config_validation.alphanumeric(value) -@given(value=text(alphabet=string.ascii_lowercase + string.digits + "_-")) +@given(value=text(alphabet=string.ascii_lowercase + string.digits + "-_")) def test_valid_name__valid(value): actual = config_validation.valid_name(value) diff --git a/tests/unit_tests/test_core.py b/tests/unit_tests/test_core.py index fd3f171275..9e4ad3d79d 100644 --- a/tests/unit_tests/test_core.py +++ b/tests/unit_tests/test_core.py @@ -444,16 +444,24 @@ class TestDefine: class TestLibrary: @pytest.mark.parametrize( - "name, value, prop, expected", + "name, version, repository, prop, expected", ( - ("mylib", None, "as_lib_dep", "mylib"), - ("mylib", None, "as_tuple", ("mylib", None)), - ("mylib", "1.2.3", "as_lib_dep", "mylib@1.2.3"), - ("mylib", "1.2.3", "as_tuple", ("mylib", "1.2.3")), + ("mylib", None, None, "as_lib_dep", "mylib"), + ("mylib", None, None, "as_tuple", ("mylib", None, None)), + ("mylib", "1.2.3", None, "as_lib_dep", "mylib@1.2.3"), + ("mylib", "1.2.3", None, "as_tuple", ("mylib", "1.2.3", None)), + ("mylib", None, "file:///test", "as_lib_dep", "mylib=file:///test"), + ( + "mylib", + None, + "file:///test", + "as_tuple", + ("mylib", None, "file:///test"), + ), ), ) - def test_properties(self, name, value, prop, expected): - target = core.Library(name, value) + def test_properties(self, name, version, repository, prop, expected): + target = core.Library(name, version, repository) actual = getattr(target, prop) @@ -465,6 +473,7 @@ class TestLibrary: ("__eq__", core.Library(name="libfoo", version="1.2.3"), True), ("__eq__", core.Library(name="libfoo", version="1.2.4"), False), ("__eq__", core.Library(name="libbar", version="1.2.3"), False), + ("__eq__", core.Library(name="libbar", version=None, repository="file:///test"), False), ("__eq__", 1000, NotImplemented), ("__eq__", "1000", NotImplemented), ("__eq__", True, NotImplemented), @@ -490,11 +499,15 @@ class TestEsphomeCore: def test_reset(self, target): """Call reset on target and compare to new instance""" - other = core.EsphomeCore() + other = core.EsphomeCore().__dict__ target.reset() + t = target.__dict__ + # ignore event loop + del other["event_loop"] + del t["event_loop"] - assert target.__dict__ == other.__dict__ + assert t == other def test_address__none(self, target): target.config = {} @@ -503,13 +516,13 @@ class TestEsphomeCore: def test_address__wifi(self, target): target.config = {} target.config[const.CONF_WIFI] = {const.CONF_USE_ADDRESS: "1.2.3.4"} - target.config["ethernet"] = {const.CONF_USE_ADDRESS: "4.3.2.1"} + target.config[const.CONF_ETHERNET] = {const.CONF_USE_ADDRESS: "4.3.2.1"} assert target.address == "1.2.3.4" def test_address__ethernet(self, target): target.config = {} - target.config["ethernet"] = {const.CONF_USE_ADDRESS: "4.3.2.1"} + target.config[const.CONF_ETHERNET] = {const.CONF_USE_ADDRESS: "4.3.2.1"} assert target.address == "4.3.2.1" diff --git a/tests/unit_tests/test_cpp_helpers.py b/tests/unit_tests/test_cpp_helpers.py index c6f37f6b5d..3e317589a9 100644 --- a/tests/unit_tests/test_cpp_helpers.py +++ b/tests/unit_tests/test_cpp_helpers.py @@ -6,25 +6,24 @@ from esphome import const from esphome.cpp_generator import MockObj -def test_gpio_pin_expression__conf_is_none(monkeypatch): - target = ch.gpio_pin_expression(None) - - actual = next(target) +@pytest.mark.asyncio +async def test_gpio_pin_expression__conf_is_none(monkeypatch): + actual = await ch.gpio_pin_expression(None) assert actual is None -def test_gpio_pin_expression__new_pin(monkeypatch): - target = ch.gpio_pin_expression( +@pytest.mark.asyncio +async def test_gpio_pin_expression__new_pin(monkeypatch): + actual = await ch.gpio_pin_expression( {const.CONF_NUMBER: 42, const.CONF_MODE: "input", const.CONF_INVERTED: False} ) - actual = next(target) - assert isinstance(actual, MockObj) -def test_register_component(monkeypatch): +@pytest.mark.asyncio +async def test_register_component(monkeypatch): var = Mock(base="foo.bar") app_mock = Mock(register_component=Mock(return_value=var)) @@ -36,9 +35,7 @@ def test_register_component(monkeypatch): add_mock = Mock() monkeypatch.setattr(ch, "add", add_mock) - target = ch.register_component(var, {}) - - actual = next(target) + actual = await ch.register_component(var, {}) assert actual is var add_mock.assert_called_once() @@ -46,18 +43,19 @@ def test_register_component(monkeypatch): assert core_mock.component_ids == [] -def test_register_component__no_component_id(monkeypatch): +@pytest.mark.asyncio +async def test_register_component__no_component_id(monkeypatch): var = Mock(base="foo.eek") core_mock = Mock(component_ids=["foo.bar"]) monkeypatch.setattr(ch, "CORE", core_mock) with pytest.raises(ValueError, match="Component ID foo.eek was not declared to"): - target = ch.register_component(var, {}) - next(target) + await ch.register_component(var, {}) -def test_register_component__with_setup_priority(monkeypatch): +@pytest.mark.asyncio +async def test_register_component__with_setup_priority(monkeypatch): var = Mock(base="foo.bar") app_mock = Mock(register_component=Mock(return_value=var)) @@ -69,7 +67,7 @@ def test_register_component__with_setup_priority(monkeypatch): add_mock = Mock() monkeypatch.setattr(ch, "add", add_mock) - target = ch.register_component( + actual = await ch.register_component( var, { const.CONF_SETUP_PRIORITY: "123", @@ -77,8 +75,6 @@ def test_register_component__with_setup_priority(monkeypatch): }, ) - actual = next(target) - assert actual is var add_mock.assert_called() assert add_mock.call_count == 3 diff --git a/tests/unit_tests/test_pins.py b/tests/unit_tests/test_pins.py index 6bc6f4d766..d2ffd5f7cd 100644 --- a/tests/unit_tests/test_pins.py +++ b/tests/unit_tests/test_pins.py @@ -11,13 +11,13 @@ import pytest from esphome.config_validation import Invalid from esphome.core import EsphomeCore -from esphome import pins +from esphome import boards, pins MOCK_ESP8266_BOARD_ID = "_mock_esp8266" MOCK_ESP8266_PINS = {"X0": 16, "X1": 5, "X2": 4, "LED": 2} MOCK_ESP8266_BOARD_ALIAS_ID = "_mock_esp8266_alias" -MOCK_ESP8266_FLASH_SIZE = pins.FLASH_SIZE_2_MB +MOCK_ESP8266_FLASH_SIZE = boards.FLASH_SIZE_2_MB MOCK_ESP32_BOARD_ID = "_mock_esp32" MOCK_ESP32_PINS = {"Y0": 12, "Y1": 8, "Y2": 3, "LED": 9, "A0": 8} @@ -31,19 +31,19 @@ def mock_mcu(monkeypatch): """ Add a mock MCU into the lists as a stable fixture """ - pins.ESP8266_BOARD_PINS[MOCK_ESP8266_BOARD_ID] = MOCK_ESP8266_PINS - pins.ESP8266_FLASH_SIZES[MOCK_ESP8266_BOARD_ID] = MOCK_ESP8266_FLASH_SIZE - pins.ESP8266_BOARD_PINS[MOCK_ESP8266_BOARD_ALIAS_ID] = MOCK_ESP8266_BOARD_ID - pins.ESP8266_FLASH_SIZES[MOCK_ESP8266_BOARD_ALIAS_ID] = MOCK_ESP8266_FLASH_SIZE - pins.ESP32_BOARD_PINS[MOCK_ESP32_BOARD_ID] = MOCK_ESP32_PINS - pins.ESP32_BOARD_PINS[MOCK_ESP32_BOARD_ALIAS_ID] = MOCK_ESP32_BOARD_ID + boards.ESP8266_BOARD_PINS[MOCK_ESP8266_BOARD_ID] = MOCK_ESP8266_PINS + boards.ESP8266_FLASH_SIZES[MOCK_ESP8266_BOARD_ID] = MOCK_ESP8266_FLASH_SIZE + boards.ESP8266_BOARD_PINS[MOCK_ESP8266_BOARD_ALIAS_ID] = MOCK_ESP8266_BOARD_ID + boards.ESP8266_FLASH_SIZES[MOCK_ESP8266_BOARD_ALIAS_ID] = MOCK_ESP8266_FLASH_SIZE + boards.ESP32_BOARD_PINS[MOCK_ESP32_BOARD_ID] = MOCK_ESP32_PINS + boards.ESP32_BOARD_PINS[MOCK_ESP32_BOARD_ALIAS_ID] = MOCK_ESP32_BOARD_ID yield - del pins.ESP8266_BOARD_PINS[MOCK_ESP8266_BOARD_ID] - del pins.ESP8266_FLASH_SIZES[MOCK_ESP8266_BOARD_ID] - del pins.ESP8266_BOARD_PINS[MOCK_ESP8266_BOARD_ALIAS_ID] - del pins.ESP8266_FLASH_SIZES[MOCK_ESP8266_BOARD_ALIAS_ID] - del pins.ESP32_BOARD_PINS[MOCK_ESP32_BOARD_ID] - del pins.ESP32_BOARD_PINS[MOCK_ESP32_BOARD_ALIAS_ID] + del boards.ESP8266_BOARD_PINS[MOCK_ESP8266_BOARD_ID] + del boards.ESP8266_FLASH_SIZES[MOCK_ESP8266_BOARD_ID] + del boards.ESP8266_BOARD_PINS[MOCK_ESP8266_BOARD_ALIAS_ID] + del boards.ESP8266_FLASH_SIZES[MOCK_ESP8266_BOARD_ALIAS_ID] + del boards.ESP32_BOARD_PINS[MOCK_ESP32_BOARD_ID] + del boards.ESP32_BOARD_PINS[MOCK_ESP32_BOARD_ALIAS_ID] @pytest.fixture diff --git a/tests/unit_tests/test_wizard.py b/tests/unit_tests/test_wizard.py index 6c952608d4..56bd5119b5 100644 --- a/tests/unit_tests/test_wizard.py +++ b/tests/unit_tests/test_wizard.py @@ -2,14 +2,14 @@ import esphome.wizard as wz import pytest -from esphome.pins import ESP8266_BOARD_PINS +from esphome.boards import ESP8266_BOARD_PINS from mock import MagicMock @pytest.fixture def default_config(): return { - "name": "test_name", + "name": "test-name", "platform": "test_platform", "board": "test_board", "ssid": "test_ssid", @@ -21,7 +21,7 @@ def default_config(): @pytest.fixture def wizard_answers(): return [ - "test_node", # Name of the node + "test-node", # Name of the node "ESP8266", # platform "nodemcuv2", # board "SSID", # ssid @@ -305,13 +305,14 @@ def test_wizard_offers_better_node_name(tmpdir, monkeypatch, wizard_answers): """ When the node name does not conform, a better alternative is offered * Removes special chars - * Replaces spaces with underscores + * Replaces spaces with hyphens + * Replaces underscores with hyphens * Converts all uppercase letters to lowercase """ # Given - wizard_answers[0] = "Küche #2" - expected_name = "kuche_2" + wizard_answers[0] = "Küche_Unten #2" + expected_name = "kuche-unten-2" monkeypatch.setattr( wz, "default_input", MagicMock(side_effect=lambda _, default: default) )